Built against OWASP LLM01Benchmark re-run every 6 hoursOpen-source SDK · MIT

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.16

Published 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-langchain

Published 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.

Next Steps