Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,45 @@ main();

**That's it!** Your existing OpenAI code now includes automatic guardrail validation based on your pipeline configuration. The response object works exactly like the original OpenAI response with additional `guardrail_results` property.

## Multi-Turn Conversations

When maintaining conversation history across multiple turns, **only append messages after guardrails pass**. This prevents blocked input messages from polluting your conversation context.

```typescript
import { GuardrailsOpenAI, GuardrailTripwireTriggered } from '@openai/guardrails';

const client = await GuardrailsOpenAI.create('./guardrails_config.json');
const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [];

while (true) {
const userInput = await readUserInput(); // replace with your input routine

try {
// ✅ Pass user input inline (don't mutate messages first)
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [...messages, { role: 'user', content: userInput }],
});

const responseContent = response.choices[0].message?.content ?? '';
console.log(`Assistant: ${responseContent}`);

// ✅ Only append AFTER guardrails pass
messages.push({ role: 'user', content: userInput });
messages.push({ role: 'assistant', content: responseContent });
} catch (error) {
if (error instanceof GuardrailTripwireTriggered) {
// ❌ Guardrail blocked - message NOT added to history
console.log('Message blocked by guardrails');
continue;
}
throw error;
}
}
```

**Why this matters**: If you append the user message before the guardrail check, blocked messages remain in your conversation history and get sent on every subsequent turn, even though they violated your safety policies.

## Guardrail Execution Error Handling

Guardrails supports two error handling modes for guardrail execution failures:
Expand Down
14 changes: 9 additions & 5 deletions examples/basic/agents_sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,26 @@ async function main(): Promise<void> {

console.log('🤔 Processing...\n');

// Pass conversation history with the new user message
const result = await run(agent, thread.concat({ role: 'user', content: userInput }));

// Update thread with the complete history including newly generated items
// Pass conversation history with the new user message (without mutating thread yet)
const conversationWithUser = thread.concat({ role: 'user', content: userInput });

const result = await run(agent, conversationWithUser);

// Guardrails passed - now safe to update thread with complete history
thread = result.history;

console.log(`Assistant: ${result.finalOutput}\n`);
} catch (error: any) {
// Handle guardrail tripwire exceptions
const errorType = error?.constructor?.name;

if (errorType === 'InputGuardrailTripwireTriggered' || error instanceof InputGuardrailTripwireTriggered) {
console.log('🛑 Input guardrail triggered! Please try a different message.\n');
// Guardrail blocked - user message NOT added to history
continue;
} else if (errorType === 'OutputGuardrailTripwireTriggered' || error instanceof OutputGuardrailTripwireTriggered) {
console.log('🛑 Output guardrail triggered! The response was blocked.\n');
// Guardrail blocked - assistant response NOT added to history
continue;
} else {
console.error('❌ An error occurred:', error.message);
Expand Down
23 changes: 19 additions & 4 deletions examples/basic/azure_example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,20 @@ const PIPELINE_CONFIG = {
* @param responseId Optional response ID for conversation tracking
* @returns Promise resolving to a response ID
*/
type ChatMessage = {
role: 'user' | 'assistant' | 'system';
content: string;
};

async function processInput(
guardrailsClient: GuardrailsAzureOpenAI,
userInput: string
userInput: string,
messages: ChatMessage[]
): Promise<string> {
// Use the new GuardrailsAzureOpenAI - it handles all guardrail validation automatically
// Pass user input inline WITHOUT mutating messages first
const response = await guardrailsClient.guardrails.chat.completions.create({
model: process.env.AZURE_DEPLOYMENT!,
messages: [{ role: 'user', content: userInput }],
messages: [...messages, { role: 'user', content: userInput }],
});

console.log(`\nAssistant output: ${response.choices[0].message.content}`);
Expand All @@ -79,6 +85,13 @@ async function processInput(
);
}

// Guardrails passed - now safe to add to conversation history
messages.push({ role: 'user', content: userInput });
messages.push({
role: 'assistant',
content: response.choices[0].message.content ?? '',
});

return response.id;
}

Expand Down Expand Up @@ -126,6 +139,7 @@ async function main(): Promise<void> {
});

const rl = createReadlineInterface();
const messages: ChatMessage[] = [];
// let responseId: string | undefined;

// Handle graceful shutdown
Expand All @@ -150,14 +164,15 @@ async function main(): Promise<void> {
}

try {
await processInput(guardrailsClient, userInput);
await processInput(guardrailsClient, userInput, messages);
} catch (error) {
if (error instanceof GuardrailTripwireTriggered) {
const stageName = error.guardrailResult.info?.stage_name || 'unknown';
console.log(`\n🛑 Guardrail triggered in stage '${stageName}'!`);
console.log('\n📋 Guardrail Result:');
console.log(JSON.stringify(error.guardrailResult, null, 2));
console.log('\nPlease rephrase your message to avoid triggering security checks.\n');
// Guardrail blocked - user message NOT added to history
} else {
console.error(`\n❌ Error: ${error instanceof Error ? error.message : String(error)}\n`);
}
Expand Down
21 changes: 11 additions & 10 deletions examples/basic/local_model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ const GEMMA3_PIPELINE_CONFIG = {
async function processInput(
guardrailsClient: GuardrailsOpenAI,
userInput: string,
inputData: OpenAI.Chat.Completions.ChatCompletionMessageParam[]
conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[]
): Promise<void> {
try {
// Use GuardrailsClient for chat completions with guardrails
// Pass user input inline WITHOUT mutating conversation history first
const response = await guardrailsClient.guardrails.chat.completions.create({
messages: [...inputData, { role: 'user', content: userInput }],
messages: [...conversation, { role: 'user', content: userInput }],
model: 'gemma3',
});

// Access response content using standard OpenAI API
const responseContent = response.choices[0].message.content;
const responseContent = response.choices[0].message.content ?? '';
console.log(`\nAssistant output: ${responseContent}\n`);

// Add to conversation history
inputData.push({ role: 'user', content: userInput });
inputData.push({ role: 'assistant', content: responseContent || '' });
// Guardrails passed - now safe to add to conversation history
conversation.push({ role: 'user', content: userInput });
conversation.push({ role: 'assistant', content: responseContent });
} catch (error) {
if (error instanceof GuardrailTripwireTriggered) {
// Handle guardrail violations
// Guardrail blocked - user message NOT added to history
throw error;
}
throw error;
Expand All @@ -69,7 +69,7 @@ async function main(): Promise<void> {
apiKey: 'ollama',
});

const inputData: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [];
const conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [];

try {
// eslint-disable-next-line no-constant-condition
Expand All @@ -87,14 +87,15 @@ async function main(): Promise<void> {
});
});

await processInput(guardrailsClient, userInput, inputData);
await processInput(guardrailsClient, userInput, conversation);
} catch (error) {
if (error instanceof GuardrailTripwireTriggered) {
const stageName = error.guardrailResult.info?.stage_name || 'unknown';
const guardrailName = error.guardrailResult.info?.guardrail_name || 'unknown';

console.log(`\n🛑 Guardrail '${guardrailName}' triggered in stage '${stageName}'!`);
console.log('Guardrail Result:', error.guardrailResult);
// Guardrail blocked - conversation history unchanged
continue;
}
throw error;
Expand Down
35 changes: 21 additions & 14 deletions examples/basic/multiturn_with_prompt_injection_detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,37 +278,38 @@ async function main(malicious: boolean = false): Promise<void> {
continue;
}

// Append user message as content parts
messages.push({
const userMessage = {
role: 'user',
content: [{ type: 'input_text', text: userInput }],
});
};

// First call: ask the model (may request function_call)
console.log(`🔄 Making initial API call...`);

let response: GuardrailsResponse;
let functionCalls: any[] = [];
let assistantOutputs: any[] = [];

try {
response = await client.guardrails.responses.create({
model: 'gpt-4.1-nano',
tools: tools,
input: messages,
input: messages.concat(userMessage),
});

printGuardrailResults('initial', response);

// Add the assistant response to conversation history
messages.push(...response.output);
assistantOutputs = response.output ?? [];

// Guardrails passed - now safe to add user message to conversation history
messages.push(userMessage);

// Grab any function calls from the response
functionCalls = response.output.filter(
(item: any) => item.type === 'function_call'
);
functionCalls = assistantOutputs.filter((item: any) => item.type === 'function_call');

// Handle the case where there are no function calls
if (functionCalls.length === 0) {
messages.push(...assistantOutputs);
console.log(`\n🤖 Assistant: ${response.output_text}`);
continue;
}
Expand All @@ -325,6 +326,7 @@ async function main(malicious: boolean = false): Promise<void> {
);
console.log(`Confidence: ${info.confidence || 'N/A'}`);
console.log('='.repeat(50));
// Guardrail blocked - user message NOT added to history
continue;
} else {
throw error;
Expand All @@ -333,6 +335,8 @@ async function main(malicious: boolean = false): Promise<void> {

if (functionCalls && functionCalls.length > 0) {
// Execute function calls and add results to conversation
const toolMessages: any[] = [];

for (const fc of functionCalls) {
const fname = fc.name;
const fargs = JSON.parse(fc.arguments);
Expand All @@ -359,20 +363,20 @@ async function main(malicious: boolean = false): Promise<void> {
};
}

messages.push({
toolMessages.push({
type: 'function_call_output',
call_id: fc.call_id,
output: JSON.stringify(result),
});
} catch (ex) {
messages.push({
toolMessages.push({
type: 'function_call_output',
call_id: fc.call_id,
output: JSON.stringify({ error: String(ex) }),
});
}
} else {
messages.push({
toolMessages.push({
type: 'function_call_output',
call_id: fc.call_id,
output: JSON.stringify({ error: `Unknown function: ${fname}` }),
Expand All @@ -386,13 +390,15 @@ async function main(malicious: boolean = false): Promise<void> {
const response = await client.guardrails.responses.create({
model: 'gpt-4.1-nano',
tools: tools,
input: messages,
input: messages.concat(assistantOutputs, toolMessages),
});

printGuardrailResults('final', response);
console.log(`\n🤖 Assistant: ${response.output_text}`);

// Add the final assistant response to conversation history
// Guardrails passed - now safe to add tool results and assistant responses to history
messages.push(...assistantOutputs);
messages.push(...toolMessages);
messages.push(...response.output);
} catch (error: any) {
if (error instanceof GuardrailTripwireTriggered) {
Expand All @@ -408,6 +414,7 @@ async function main(malicious: boolean = false): Promise<void> {
console.log(`Observation: ${info.observation || 'N/A'}`);
console.log(`Confidence: ${info.confidence || 'N/A'}`);
console.log('='.repeat(50));
// Guardrail blocked - tool results NOT added to history
continue;
} else {
throw error;
Expand Down