Guías de integración

Patrón general: check input → llama LLM → validate output → responde.

OpenAI

import OpenAI from 'openai';
import { PromptShield } from '@aisociety/promptshield';

const ps = new PromptShield({ apiKey: process.env.PROMPTSHIELD_API_KEY! });
const openai = new OpenAI();

export async function chat(userMessage: string, sessionId: string) {
  const check = await ps.check({ message: userMessage, sessionId });
  if (!check.safe) return { blocked: true, reason: check.reason };

  const completion = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: userMessage }],
  });
  const reply = completion.choices[0]?.message?.content ?? '';

  const out = await ps.validateOutput({ response: reply, sessionId });
  if (!out.safe) return { blocked: true, reason: out.reason };
  return { reply };
}

Anthropic

import Anthropic from '@anthropic-ai/sdk';
import { PromptShield } from '@aisociety/promptshield';

const ps = new PromptShield({ apiKey: process.env.PROMPTSHIELD_API_KEY! });
const anthropic = new Anthropic();

export async function ask(userMessage: string) {
  const check = await ps.check({ message: userMessage });
  if (!check.safe) throw new Error(`blocked: ${check.reason}`);

  const msg = await anthropic.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    messages: [{ role: 'user', content: userMessage }],
  });
  const reply = msg.content
    .filter((b) => b.type === 'text')
    .map((b) => (b as { type: 'text'; text: string }).text)
    .join('');

  const out = await ps.validateOutput({ response: reply });
  if (!out.safe) throw new Error(`output blocked: ${out.reason}`);
  return reply;
}

Vercel AI SDK

Inserta el check antes de streamText y valida la respuesta al final del stream.

import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { PromptShield } from '@aisociety/promptshield';

const ps = new PromptShield({ apiKey: process.env.PROMPTSHIELD_API_KEY! });

export async function POST(req: Request) {
  const { messages } = await req.json();
  const last = messages[messages.length - 1];
  const userMessage = typeof last.content === 'string' ? last.content : '';

  const check = await ps.check({ message: userMessage });
  if (!check.safe) {
    return new Response(JSON.stringify({ error: 'blocked', reason: check.reason }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  const result = streamText({
    model: openai('gpt-4o-mini'),
    messages,
    onFinish: async ({ text }) => {
      const out = await ps.validateOutput({ response: text });
      if (!out.safe) {
        // log + alert your team — the response already streamed.
        console.warn('output flagged', out.reason);
      }
    },
  });

  return result.toDataStreamResponse();
}

Subida de documentos

import { PromptShield } from '@aisociety/promptshield';
import { readFileSync } from 'node:fs';

const ps = new PromptShield({ apiKey: process.env.PROMPTSHIELD_API_KEY! });

const scan = await ps.scanDocument({
  file: readFileSync('./contrato.pdf'),
  filename: 'contrato.pdf',
  mimetype: 'application/pdf',
});

if (scan.status === 'completed') {
  if (!scan.safe) throw new Error(`unsafe: ${scan.reason}`);
} else {
  // Async (>2 MB) — poll
  let s = await ps.getScanStatus(scan.jobId);
  while (s.status === 'queued' || s.status === 'processing') {
    await new Promise((r) => setTimeout(r, 1000));
    s = await ps.getScanStatus(scan.jobId);
  }
  if (s.status !== 'completed' || !s.result?.safe) {
    throw new Error('unsafe document');
  }
}