Skip to content

Commit d934a65

Browse files
authored
Dev/steven/update convo history examples (#20)
* Update examples with best practices
1 parent 6d49307 commit d934a65

File tree

5 files changed

+99
-33
lines changed

5 files changed

+99
-33
lines changed

docs/quickstart.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,45 @@ main();
8383

8484
**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.
8585

86+
## Multi-Turn Conversations
87+
88+
When maintaining conversation history across multiple turns, **only append messages after guardrails pass**. This prevents blocked input messages from polluting your conversation context.
89+
90+
```typescript
91+
import { GuardrailsOpenAI, GuardrailTripwireTriggered } from '@openai/guardrails';
92+
93+
const client = await GuardrailsOpenAI.create('./guardrails_config.json');
94+
const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [];
95+
96+
while (true) {
97+
const userInput = await readUserInput(); // replace with your input routine
98+
99+
try {
100+
// ✅ Pass user input inline (don't mutate messages first)
101+
const response = await client.chat.completions.create({
102+
model: 'gpt-4o',
103+
messages: [...messages, { role: 'user', content: userInput }],
104+
});
105+
106+
const responseContent = response.choices[0].message?.content ?? '';
107+
console.log(`Assistant: ${responseContent}`);
108+
109+
// ✅ Only append AFTER guardrails pass
110+
messages.push({ role: 'user', content: userInput });
111+
messages.push({ role: 'assistant', content: responseContent });
112+
} catch (error) {
113+
if (error instanceof GuardrailTripwireTriggered) {
114+
// ❌ Guardrail blocked - message NOT added to history
115+
console.log('Message blocked by guardrails');
116+
continue;
117+
}
118+
throw error;
119+
}
120+
}
121+
```
122+
123+
**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.
124+
86125
## Guardrail Execution Error Handling
87126

88127
Guardrails supports two error handling modes for guardrail execution failures:

examples/basic/agents_sdk.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,22 +112,26 @@ async function main(): Promise<void> {
112112

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

115-
// Pass conversation history with the new user message
116-
const result = await run(agent, thread.concat({ role: 'user', content: userInput }));
117-
118-
// Update thread with the complete history including newly generated items
115+
// Pass conversation history with the new user message (without mutating thread yet)
116+
const conversationWithUser = thread.concat({ role: 'user', content: userInput });
117+
118+
const result = await run(agent, conversationWithUser);
119+
120+
// Guardrails passed - now safe to update thread with complete history
119121
thread = result.history;
120-
122+
121123
console.log(`Assistant: ${result.finalOutput}\n`);
122124
} catch (error: any) {
123125
// Handle guardrail tripwire exceptions
124126
const errorType = error?.constructor?.name;
125127

126128
if (errorType === 'InputGuardrailTripwireTriggered' || error instanceof InputGuardrailTripwireTriggered) {
127129
console.log('🛑 Input guardrail triggered! Please try a different message.\n');
130+
// Guardrail blocked - user message NOT added to history
128131
continue;
129132
} else if (errorType === 'OutputGuardrailTripwireTriggered' || error instanceof OutputGuardrailTripwireTriggered) {
130133
console.log('🛑 Output guardrail triggered! The response was blocked.\n');
134+
// Guardrail blocked - assistant response NOT added to history
131135
continue;
132136
} else {
133137
console.error('❌ An error occurred:', error.message);

examples/basic/azure_example.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,20 @@ const PIPELINE_CONFIG = {
6060
* @param responseId Optional response ID for conversation tracking
6161
* @returns Promise resolving to a response ID
6262
*/
63+
type ChatMessage = {
64+
role: 'user' | 'assistant' | 'system';
65+
content: string;
66+
};
67+
6368
async function processInput(
6469
guardrailsClient: GuardrailsAzureOpenAI,
65-
userInput: string
70+
userInput: string,
71+
messages: ChatMessage[]
6672
): Promise<string> {
67-
// Use the new GuardrailsAzureOpenAI - it handles all guardrail validation automatically
73+
// Pass user input inline WITHOUT mutating messages first
6874
const response = await guardrailsClient.guardrails.chat.completions.create({
6975
model: process.env.AZURE_DEPLOYMENT!,
70-
messages: [{ role: 'user', content: userInput }],
76+
messages: [...messages, { role: 'user', content: userInput }],
7177
});
7278

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

88+
// Guardrails passed - now safe to add to conversation history
89+
messages.push({ role: 'user', content: userInput });
90+
messages.push({
91+
role: 'assistant',
92+
content: response.choices[0].message.content ?? '',
93+
});
94+
8295
return response.id;
8396
}
8497

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

128141
const rl = createReadlineInterface();
142+
const messages: ChatMessage[] = [];
129143
// let responseId: string | undefined;
130144

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

152166
try {
153-
await processInput(guardrailsClient, userInput);
167+
await processInput(guardrailsClient, userInput, messages);
154168
} catch (error) {
155169
if (error instanceof GuardrailTripwireTriggered) {
156170
const stageName = error.guardrailResult.info?.stage_name || 'unknown';
157171
console.log(`\n🛑 Guardrail triggered in stage '${stageName}'!`);
158172
console.log('\n📋 Guardrail Result:');
159173
console.log(JSON.stringify(error.guardrailResult, null, 2));
160174
console.log('\nPlease rephrase your message to avoid triggering security checks.\n');
175+
// Guardrail blocked - user message NOT added to history
161176
} else {
162177
console.error(`\n❌ Error: ${error instanceof Error ? error.message : String(error)}\n`);
163178
}

examples/basic/local_model.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,25 @@ const GEMMA3_PIPELINE_CONFIG = {
3434
async function processInput(
3535
guardrailsClient: GuardrailsOpenAI,
3636
userInput: string,
37-
inputData: OpenAI.Chat.Completions.ChatCompletionMessageParam[]
37+
conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[]
3838
): Promise<void> {
3939
try {
40-
// Use GuardrailsClient for chat completions with guardrails
40+
// Pass user input inline WITHOUT mutating conversation history first
4141
const response = await guardrailsClient.guardrails.chat.completions.create({
42-
messages: [...inputData, { role: 'user', content: userInput }],
42+
messages: [...conversation, { role: 'user', content: userInput }],
4343
model: 'gemma3',
4444
});
4545

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

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

72-
const inputData: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [];
72+
const conversation: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [];
7373

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

90-
await processInput(guardrailsClient, userInput, inputData);
90+
await processInput(guardrailsClient, userInput, conversation);
9191
} catch (error) {
9292
if (error instanceof GuardrailTripwireTriggered) {
9393
const stageName = error.guardrailResult.info?.stage_name || 'unknown';
9494
const guardrailName = error.guardrailResult.info?.guardrail_name || 'unknown';
9595

9696
console.log(`\n🛑 Guardrail '${guardrailName}' triggered in stage '${stageName}'!`);
9797
console.log('Guardrail Result:', error.guardrailResult);
98+
// Guardrail blocked - conversation history unchanged
9899
continue;
99100
}
100101
throw error;

examples/basic/multiturn_with_prompt_injection_detection.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -278,37 +278,38 @@ async function main(malicious: boolean = false): Promise<void> {
278278
continue;
279279
}
280280

281-
// Append user message as content parts
282-
messages.push({
281+
const userMessage = {
283282
role: 'user',
284283
content: [{ type: 'input_text', text: userInput }],
285-
});
284+
};
286285

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

290289
let response: GuardrailsResponse;
291290
let functionCalls: any[] = [];
291+
let assistantOutputs: any[] = [];
292292

293293
try {
294294
response = await client.guardrails.responses.create({
295295
model: 'gpt-4.1-nano',
296296
tools: tools,
297-
input: messages,
297+
input: messages.concat(userMessage),
298298
});
299299

300300
printGuardrailResults('initial', response);
301301

302-
// Add the assistant response to conversation history
303-
messages.push(...response.output);
302+
assistantOutputs = response.output ?? [];
303+
304+
// Guardrails passed - now safe to add user message to conversation history
305+
messages.push(userMessage);
304306

305307
// Grab any function calls from the response
306-
functionCalls = response.output.filter(
307-
(item: any) => item.type === 'function_call'
308-
);
308+
functionCalls = assistantOutputs.filter((item: any) => item.type === 'function_call');
309309

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

334336
if (functionCalls && functionCalls.length > 0) {
335337
// Execute function calls and add results to conversation
338+
const toolMessages: any[] = [];
339+
336340
for (const fc of functionCalls) {
337341
const fname = fc.name;
338342
const fargs = JSON.parse(fc.arguments);
@@ -359,20 +363,20 @@ async function main(malicious: boolean = false): Promise<void> {
359363
};
360364
}
361365

362-
messages.push({
366+
toolMessages.push({
363367
type: 'function_call_output',
364368
call_id: fc.call_id,
365369
output: JSON.stringify(result),
366370
});
367371
} catch (ex) {
368-
messages.push({
372+
toolMessages.push({
369373
type: 'function_call_output',
370374
call_id: fc.call_id,
371375
output: JSON.stringify({ error: String(ex) }),
372376
});
373377
}
374378
} else {
375-
messages.push({
379+
toolMessages.push({
376380
type: 'function_call_output',
377381
call_id: fc.call_id,
378382
output: JSON.stringify({ error: `Unknown function: ${fname}` }),
@@ -386,13 +390,15 @@ async function main(malicious: boolean = false): Promise<void> {
386390
const response = await client.guardrails.responses.create({
387391
model: 'gpt-4.1-nano',
388392
tools: tools,
389-
input: messages,
393+
input: messages.concat(assistantOutputs, toolMessages),
390394
});
391395

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

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

0 commit comments

Comments
 (0)