LangChain
The langchain-expunct package is drop-in PII redaction middleware for LangChain. Add one line to your chain and automatically strip sensitive data before it reaches any LLM.
Package: langchain-expunct on PyPI
Installation
pip install langchain-expunctGet your API key at expunct.ai — free tier includes 1M tokens/month.
Quick Start
Redact text in a chain
from langchain_openai import ChatOpenAI
from langchain_expunct import ExpunctPIIRedactor
redactor = ExpunctPIIRedactor(api_key="pk_live_...")
llm = ChatOpenAI()
chain = redactor | llm
result = chain.invoke("Summarize this: John Smith (SSN 219-09-9999) applied for a loan.")
# The LLM sees: "Summarize this: PERSON_1 (SSN US_SSN_1) applied for a loan."Redact chat messages
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langchain_expunct import ExpunctChatMessageRedactor
redactor = ExpunctChatMessageRedactor(api_key="pk_live_...")
chat = ChatOpenAI()
chain = redactor | chat
result = chain.invoke([
SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="My name is Alice Johnson and my email is alice@example.com"),
])Async usage
import asyncio
from langchain_openai import ChatOpenAI
from langchain_expunct import ExpunctPIIRedactor
redactor = ExpunctPIIRedactor(api_key="pk_live_...")
llm = ChatOpenAI()
chain = redactor | llm
async def main():
result = await chain.ainvoke("Call Bob at 415-555-0100")
print(result)
asyncio.run(main())Callback handler — auto-redact LLM inputs
from langchain_openai import ChatOpenAI
from langchain_expunct import ExpunctCallbackHandler
handler = ExpunctCallbackHandler(api_key="pk_live_...", redact_input=True, log_output=True)
llm = ChatOpenAI()
result = llm.invoke(
"Tell me about John Smith, SSN 219-09-9999",
config={"callbacks": [handler]},
)For async workflows, use AsyncExpunctCallbackHandler:
from langchain_expunct import AsyncExpunctCallbackHandler
handler = AsyncExpunctCallbackHandler(api_key="pk_live_...")
result = await llm.ainvoke("...", config={"callbacks": [handler]})Components
ExpunctPIIRedactor
A Runnable[str, str] that redacts PII from plain text. Composable with |.
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | required | Your Expunct API key |
base_url | str | https://api.expunct.ai | API base URL |
policy_id | str | None | None | Policy to apply |
language | str | "en" | Language of input text |
ExpunctChatMessageRedactor
A Runnable[list[BaseMessage], list[BaseMessage]] that redacts PII from messages while preserving roles, names, and metadata.
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | required | Your Expunct API key |
base_url | str | https://api.expunct.ai | API base URL |
language | str | "en" | Language of input text |
ExpunctCallbackHandler / AsyncExpunctCallbackHandler
Hooks into on_llm_start and on_llm_end to redact inputs and log detected PII in outputs.
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | required | Your Expunct API key |
base_url | str | https://api.expunct.ai | API base URL |
language | str | "en" | Language of input text |
redact_input | bool | True | Redact PII from prompts on LLM start |
log_output | bool | True | Log detected PII in LLM responses |
RAG Pipeline Example
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_expunct import ExpunctPIIRedactor
vectorstore = FAISS.from_texts(["..."], embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
redactor = ExpunctPIIRedactor(api_key="pk_live_...")
prompt = ChatPromptTemplate.from_template(
"Answer based on context: {context}\n\nQuestion: {question}"
)
llm = ChatOpenAI()
rag_chain = (
{"context": retriever, "question": redactor}
| prompt
| llm
| StrOutputParser()
)
result = rag_chain.invoke("What did John Smith order? His email is john@acme.com")