Skip to main content

LangGraph integration for PlainID authorization

Project description

langgraph-plainid

PlainID authorization integration for LangGraph. Provides LangGraph nodes for prompt categorization, text anonymization, and policy-based document retrieval that can be composed into stateful agent graphs.

This library depends on core-plainid and langchain-plainid for the underlying authorization components. Please refer to the core-plainid README for setting up permissions, categorizers, anonymizers, classifiers, and PlainID rulesets, and the langchain-plainid README for retrieval and vector store configuration.

All nodes fully support both synchronous and asynchronous execution. The examples below use the async API; replace ainvoke with invoke for synchronous usage.

Installation

pip install langgraph-plainid

core-plainid and langchain-plainid are installed automatically as dependencies. Optional extras from core-plainid can be installed directly through langgraph-plainid:

pip install langgraph-plainid[categorization-llm]        # LLM-based categorization via LiteLLM
pip install langgraph-plainid[categorization-zeroshot]   # Zero-shot classification via Hugging Face
pip install langgraph-plainid[anonymization]             # Presidio-based PII anonymization
pip install langgraph-plainid[anonymization-ahds]        # Anonymization + Azure Health De-identification
pip install langgraph-plainid[all]                       # Everything

Agent State

All nodes operate on a shared AgentState TypedDict that flows through the graph. The state contains the request_context for identity information and optional sub-states for each node type. Alternatively, request_context can be provided at construction time to the underlying components (e.g. PlainIDPermissionsProvider, FilterDirectiveProvider) — see the core-plainid README for details.

from langgraph_plainid.models.state.agent_state import AgentState
Field Type Description
request_context RequestContext Identity context (entity ID, type, additional identities)
categorization CategorizationState Sub-state with query and optional error_details
anonymization AnonymizationState Sub-state with query, optional output_text, and error_details
retrieval RetrievalState Sub-state with query, resource_types, optional retrieved_documents, and error_details
sql_authorization SQLAuthorizationState Sub-state with query, options, optional output_sql, was_modified, and error_details

Multiple identities (e.g. a User and an AI Agent) are supported for agentic scenarios through the additional_identities field in RequestContext. Identity can also be resolved via HTTP headers that are matched against configured values in PlainID — see the Identity Context section in the core-plainid README for details.

All core-plainid components constructed in this library (e.g. PlainIDPermissionsProvider, FilterDirectiveProvider) support three authentication modes: client credentials, per-request JWT token, and automatic IDP token management via IdpAuthProvider — see the Authentication section in the core-plainid README.

Base Node

All PlainID nodes extend BaseNode, which provides common behavior:

  • next_node — optional name of the next node to route to via LangGraph Command. If not set, the node returns the updated state directly and graph edges determine the flow.
  • next_node_on_error — optional name of a node to route to when an error occurs. If set, errors are caught and routed as ErrorDetails in the sub-state. If not set, exceptions propagate normally.

Categorization Node

The CategorizationNode classifies the input prompt against PlainID policies. If the categories are not allowed, a PlainIDCategorizerException is raised (or routed to the error handler node if configured).

For setting up the categorizer, classifier providers, and the PlainID Prompt_Control ruleset, see the Category Filtering section in the core-plainid README.

from core_plainid.categorization.categorizer import Categorizer
from core_plainid.utils.plainid_permissions_provider import PlainIDPermissionsProvider
from langgraph_plainid.nodes.categorization_node import CategorizationNode

permissions_provider = PlainIDPermissionsProvider(
    base_url="https://platform-product.us1.plainid.io",
    client_id="your_client_id",
    client_secret="your_client_secret",
)

categorizer = Categorizer(
    classifier_provider=classifier,
    permissions_provider=permissions_provider,
    all_categories=["contract", "HR", "finance"],
)

categorization_node = CategorizationNode(
    categorizer=categorizer,
    next_node="anonymizer",
    next_node_on_error="error_handler",
)

The node reads its input from state["categorization"]["query"].

Anonymization Node

The AnonymizerNode detects and anonymizes PII in the input text based on PlainID policies. The anonymized text is written to state["anonymization"]["output_text"].

For setting up the anonymizer, encryption key, AHDS, and the PlainID Output_Control ruleset, see the Anonymization section in the core-plainid README.

from core_plainid.anonymization.presidio_anonymizer import PresidioAnonymizer
from core_plainid.utils.plainid_permissions_provider import PlainIDPermissionsProvider
from langgraph_plainid.nodes.anonymizer_node import AnonymizerNode

permissions_provider = PlainIDPermissionsProvider(
    base_url="https://platform-product.us1.plainid.io",
    client_id="your_client_id",
    client_secret="your_client_secret",
)

anonymizer = PresidioAnonymizer(
    permissions_provider=permissions_provider,
    encrypt_key="your_16_char_key!",
)

anonymizer_node = AnonymizerNode(
    anonymizer=anonymizer,
    next_node="retrieval",
    next_node_on_error="error_handler",
)

The node reads its input from state["anonymization"]["query"] and writes the result to state["anonymization"]["output_text"].

Retrieval Node

The RetrievalNode retrieves documents from vector stores with PlainID-enforced filters. The retrieved documents are written to state["retrieval"]["retrieved_documents"].

For setting up the retriever, filter provider, and vector store configuration, see the Retrieval section in the langchain-plainid README.

from langchain_plainid.retrieval.filter_directive_provider import FilterDirectiveProvider
from langchain_plainid.retrieval.multi_store_retriever import MultiStoreRetriever
from langgraph_plainid.nodes.retrieval_node import RetrievalNode

filter_provider = FilterDirectiveProvider(
    base_url="https://platform-product.us1.plainid.io",
    client_id="your_client_id",
    client_secret="your_client_secret",
)

retriever = MultiStoreRetriever(
    filter_provider=filter_provider,
    resource_types=["customer"],
    vector_stores=[customer_store],
    k=4,
)

retrieval_node = RetrievalNode(
    retriever=retriever,
    next_node_on_error="error_handler",
)

The node reads its input from state["retrieval"]["query"] and writes the result to state["retrieval"]["retrieved_documents"].

SQL Authorization Node

The SQLAuthorizationNode authorizes SQL queries via the PlainID SQL Authorizer, applying row-level and column-level security. The modified SQL is written to state["sql_authorization"]["output_sql"] along with state["sql_authorization"]["was_modified"].

The node reads its SQL query from state["sql_authorization"]["query"] and the configuration from state["sql_authorization"]["options"] (an SQLAuthorizerOptions instance). Entity identity (entity_id, entity_type_id) is resolved from state["request_context"].

from core_plainid.clients.plainid_sql_authorizer_client import PlainIDSQLAuthorizerClient
from core_plainid.models.sql_authorization.sql_authorizer_options import (
    SQLAuthorizerFlagsOptions,
    SQLAuthorizerOptions,
)
from langgraph_plainid.nodes.sql_authorization_node import SQLAuthorizationNode

sql_client = PlainIDSQLAuthorizerClient(
    base_url="https://platform-product.us1.plainid.io",
    client_id="your_client_id",
    client_secret="your_client_secret",
)

sql_node = SQLAuthorizationNode(
    sql_authorizer_client=sql_client,
    next_node="next_step",
    next_node_on_error="error_handler",
)

When invoking the graph, provide the SQL query and options in the initial state:

options = SQLAuthorizerOptions(
    flags=SQLAuthorizerFlagsOptions(expand_star_column=True),
)

result = await app.ainvoke({
    "request_context": request_context,
    "sql_authorization": {
        "query": "SELECT * FROM employees",
        "options": options,
    },
})

print(result["sql_authorization"]["output_sql"])    # modified SQL
print(result["sql_authorization"]["was_modified"])  # True if policies applied

Limitation

The SQL Authorizer does not currently support the runtime interface for primary and secondary identities (User + Agent). Only a single identity context is supported.

Building a Graph

Nodes are composed into a LangGraph StateGraph to define the agent's execution flow. Here is an example graph that categorizes a prompt, anonymizes it, and then retrieves documents:

from langgraph.graph import END, START, StateGraph
from core_plainid.models.context.request_context import RequestContext
from langgraph_plainid.models.state.agent_state import AgentState

graph = StateGraph(AgentState)

graph.add_node("categorization", categorization_node)
graph.add_node("anonymizer", anonymizer_node)
graph.add_node("retrieval", retrieval_node)

graph.add_edge(START, "categorization")
graph.add_edge("categorization", "anonymizer")
graph.add_edge("anonymizer", "retrieval")
graph.add_edge("retrieval", END)

app = graph.compile()

request_context = RequestContext(
    entity_id="your_entity_id",
    entity_type_id="your_entity_type",
)

result = await app.ainvoke({
    "request_context": request_context,
    "categorization": {"query": "What is John Smith's contract status?"},
    "anonymization": {"query": "What is John Smith's contract status?"},
    "retrieval": {"query": "What is John Smith's contract status?"},
})

print(result["anonymization"]["output_text"])       # anonymized text
print(result["retrieval"]["retrieved_documents"])    # retrieved documents

Using Command-Based Routing

Instead of defining edges between all nodes, you can use the next_node parameter to let nodes route to the next step via LangGraph Command:

categorization_node = CategorizationNode(
    categorizer=categorizer,
    next_node="anonymizer",
)

anonymizer_node = AnonymizerNode(
    anonymizer=anonymizer,
    next_node="retrieval",
)

retrieval_node = RetrievalNode(retriever=retriever)

graph = StateGraph(AgentState)

graph.add_node("categorization", categorization_node)
graph.add_node("anonymizer", anonymizer_node)
graph.add_node("retrieval", retrieval_node)

graph.add_edge(START, "categorization")
graph.add_edge("retrieval", END)

app = graph.compile()

Error Handling

When next_node_on_error is set, errors are caught and the graph routes to the specified error handler node. The error details are available in the sub-state:

def error_handler(state: AgentState) -> dict:
    for key in ["categorization", "anonymization", "retrieval", "sql_authorization"]:
        sub_state = state.get(key)
        
        if sub_state and sub_state.get("error_details"):
            error_details = sub_state["error_details"]
            
            print(f"Error in {key}: {error_details['error_message']}")
            print(f"Exception: {error_details['error']}")

    return {}

graph.add_node("error_handler", error_handler)

If next_node_on_error is not set, exceptions propagate normally and can be caught in the calling context of invoke / ainvoke.

Exceptions

All exceptions are defined in the core-plainid library. See the Exceptions section in the core-plainid README for the full list.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langgraph_plainid-2.1.0-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

File details

Details for the file langgraph_plainid-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: langgraph_plainid-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 10.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for langgraph_plainid-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b328b99ff99161e2551111e5f0ece0df15722e4de9a39b1e7078e5f7e111955d
MD5 f5005bdad188801009577db820dc8b88
BLAKE2b-256 413e2667ffd8c4df754e8fb67af2467eb91ab8482fea4c2cb1275725068ed702

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page