LangChain
SafePrompt ships an official LangChain callback handler for both runtimes: safeprompt-langchain on PyPI and @safeprompt.dev/langchain on npm. Attach the handler to the model and tool events your workflow uses. Verify callback propagation, text serialization and blocking behavior before enabling screening.
The JS/TS guide is below. Jump to Python.
Install
npm install @safeprompt.dev/langchain@0.2.0 @langchain/core@1.2.17 @langchain/openai@1.6.2 langchain@1.5.16Published JavaScript peer dependency: @langchain/core >=1.0.0 <2.0.0. The examples use the package versions above and Node.js 22 or newer. You will also need a SafePrompt API key from the dashboard.
Quick Start
Pass a SafePromptCallbackHandler through your workflow's callbacks configuration. Verify that it receives each intended model event and that SafePromptBlockedErrorstops the model call in your installed LangChain version.
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import {
SafePromptCallbackHandler,
SafePromptBlockedError,
} from '@safeprompt.dev/langchain';
async function answer(userInput: string, clientIP: string) {
const handler = new SafePromptCallbackHandler({
apiKey: process.env.SAFEPROMPT_API_KEY!,
userIP: clientIP, // actual end-user IP from your trusted server
enforcement: 'block',
onProviderError: 'fail-closed',
});
handler.raiseError = true;
handler.awaitHandlers = true;
const chain = ChatPromptTemplate.fromTemplate('Answer: {input}')
.pipe(new ChatOpenAI({ model: 'gpt-4o-mini' }));
try {
const message = await chain.invoke(
{ input: userInput },
{ callbacks: [handler] },
);
return { text: message.content };
} catch (err) {
if (err instanceof SafePromptBlockedError) {
return { error: 'Prompt blocked for safety', threats: err.result.threats };
}
throw err;
}
}Await the handler and propagate its errors with awaitHandlers and raiseError. The published handler inherits background callbacks and suppressed errors by default in these LangChain versions. Test unsafe and unavailable checks before allowing model use.
Configuration
const handler = new SafePromptCallbackHandler({
apiKey: process.env.SAFEPROMPT_API_KEY!,
userIP: '203.0.113.1', // REQUIRED — end-user IP
provider: 'https://api.safeprompt.dev', // default
enforcement: 'block', // 'block' | 'log'
onProviderError: 'fail-closed', // 'fail-closed' | 'fail-open'
sampleRate: 1.0, // 0..1 — fraction of prompts to screen
onBlock: (prompt, result) => {
console.warn('[safeprompt] blocked', result.threats);
},
onError: (prompt, err) => {
console.error('[safeprompt] provider error', err.message);
},
});
handler.raiseError = true;
handler.awaitHandlers = true;onProviderError decides what happens if SafePrompt itself is unreachable: fail-closed blocks the request, fail-open lets it through and fires onError. Choose deliberately for your risk profile.
Roll Out in Log Mode First
Set enforcement: 'log' and onProviderError: 'fail-open' to observe without blocking. You still get onBlock events for anything it would have blocked, so you can watch real traffic in your logs or the SafePrompt dashboard, tune custom lists, then flip to enforcement: 'block' once you are confident.
new SafePromptCallbackHandler({
apiKey: process.env.SAFEPROMPT_API_KEY!,
userIP: req.ip,
enforcement: 'log', // observe only — never throws
onProviderError: 'fail-open',
onBlock: (prompt, result) => {
logger.warn({ threats: result.threats }, 'would block');
},
});Agents & Indirect Injection
The JavaScript tool-end callback checks nonempty string outputs; non-string outputs are skipped at that event. Verify the exact text reaching the next model call and test that unsafe or unavailable checks stop it. Where callback coverage is incomplete, screen that text through the HTTP API before forwarding it.
import { createAgent } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import type { StructuredToolInterface } from '@langchain/core/tools';
import { SafePromptCallbackHandler } from '@safeprompt.dev/langchain';
async function runAgent(userInput: string, clientIP: string, tools: StructuredToolInterface[]) {
const handler = new SafePromptCallbackHandler({
apiKey: process.env.SAFEPROMPT_API_KEY!,
userIP: clientIP,
enforcement: 'block',
onProviderError: 'fail-closed',
});
handler.raiseError = true;
handler.awaitHandlers = true;
const agent = createAgent({
model: new ChatOpenAI({ model: 'gpt-4o-mini' }),
tools,
});
return agent.invoke(
{ messages: [{ role: 'user', content: userInput }] },
{ callbacks: [handler] },
);
}Python
The safeprompt-langchain package exposes SafePromptCallbackHandler, a BaseCallbackHandler you pass in the callbacks list of any chain or agent.
Install
pip install safeprompt-langchainPublished Python package version 0.2.1 requires Python 3.9 or newer and langchain-core >=0.3.0. Its dependency requirements differ from the JavaScript package.
Quick start
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from safeprompt_langchain import SafePromptCallbackHandler, SafePromptBlockedError
handler = SafePromptCallbackHandler(
api_key="sp_live_...", # get one at https://safeprompt.dev
user_ip=request_ip, # end-user IP from your web framework
)
chain = ChatPromptTemplate.from_template("Answer: {input}") | ChatOpenAI(model="gpt-4o-mini")
try:
result = chain.invoke({"input": user_input}, config={"callbacks": [handler]})
print(result.content)
except SafePromptBlockedError as err:
# The prompt never reached the model.
return "That request looked unsafe and was not sent."api_key and user_ip are both required. The end-user IP is what powers abuse correlation across requests, so passing a real one matters.
Options
SafePromptCallbackHandler(
api_key="sp_live_...",
user_ip="203.0.113.1", # REQUIRED, end-user IP
provider="https://api.safeprompt.dev", # default
enforcement="block", # "block" raises, "log" only reports
on_provider_error="fail-closed", # "fail-closed" | "fail-open"
sample_rate=1.0, # fraction of calls validated
timeout=30.0, # seconds
on_block=None, # callable(text, result)
on_error=None, # callable(exc)
)Roll out in log mode first
Set enforcement="log" to see what would have been blocked without blocking anything. Nothing is rejected, and on_block still fires, so you can measure the real false-positive rate against your own traffic before enforcing.
handler = SafePromptCallbackHandler(
api_key="sp_live_...",
user_ip=request_ip,
enforcement="log",
on_block=lambda text, result: logger.warning("would have blocked: %s", result),
)Fail-closed by default
If SafePrompt itself cannot be reached, on_provider_error="fail-closed" raises rather than letting an unchecked prompt through. That is the default on purpose. Set "fail-open" only if availability matters more to you than the check.