-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy path10-guardrails.ts
More file actions
204 lines (185 loc) · 6.52 KB
/
Copy path10-guardrails.ts
File metadata and controls
204 lines (185 loc) · 6.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
* 10 - Guardrails — output validation with tool calls.
*
* Demonstrates guardrails that catch PII leaking from tool results into
* the agent's final answer. The agent uses two tools:
*
* 1. get_order_status — returns safe order data (no PII)
* 2. get_customer_info — returns data that includes a credit card number
*
* Three guardrail types are shown:
* - RegexGuardrail: server-side pattern matching to block PII
* - LLMGuardrail: LLM-based policy check for sensitive data
* - Custom guardrail function (via guardrail())
*
* The RegexGuardrail is the primary PII blocker (runs server-side).
* If the agent includes the raw credit card number in its response,
* the guardrail fails with onFail="retry" — the agent retries with
* feedback asking it to redact the PII.
*
* Requirements:
* - Conductor server with LLM support
* - CONDUCTOR_SERVER_URL=http://localhost:8080/api
* - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini
*/
import {
Agent,
AgentRuntime,
RegexGuardrail,
LLMGuardrail,
guardrail,
tool,
} from '@io-orkes/conductor-javascript/agents';
import type { GuardrailResult } from '@io-orkes/conductor-javascript/agents';
import { llmModel } from './settings';
// ── Tools ─────────────────────────────────────────────────
const getOrderStatus = tool(
async (args: { orderId: string }) => {
return {
order_id: args.orderId,
status: 'shipped',
tracking: '1Z999AA10123456784',
estimated_delivery: '2026-02-22',
};
},
{
name: 'get_order_status',
description: 'Look up the current status of an order.',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string', description: 'The order ID to look up' },
},
required: ['orderId'],
},
},
);
const getCustomerInfo = tool(
async (args: { customerId: string }) => {
// This tool returns data with PII — the guardrail should catch it
// if the agent includes it verbatim in the response.
return {
customer_id: args.customerId,
name: 'Alice Johnson',
email: 'alice@example.com',
card_on_file: '4532-0150-1234-5678', // PII!
membership: 'gold',
};
},
{
name: 'get_customer_info',
description: 'Retrieve customer details including payment info on file.',
inputSchema: {
type: 'object',
properties: {
customerId: { type: 'string', description: 'The customer ID to look up' },
},
required: ['customerId'],
},
},
);
// ── RegexGuardrail: block PII patterns (server-side) ──────
const piiBlocker = new RegexGuardrail({
name: 'pii_blocker',
patterns: [
'\\b\\d{3}-\\d{2}-\\d{4}\\b', // SSN
'\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b', // Credit card
],
mode: 'block',
position: 'output',
onFail: 'retry',
message: 'PII detected (credit card or SSN). Please redact all personal information.',
});
// ── LLMGuardrail: policy-based sensitive data check ───────
const sensitiveDataChecker = new LLMGuardrail({
name: 'sensitive_data_checker',
model: 'anthropic/claude-sonnet-4-6',
policy:
'Check if the response contains any sensitive personal information ' +
'such as full credit card numbers, SSNs, or passwords. ' +
'If found, request redaction.',
position: 'output',
onFail: 'fix',
maxTokens: 5000,
});
// ── Custom guardrail function (local validation logic) ────
const noPii = guardrail(
(content: string): GuardrailResult => {
const ccPattern = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/;
const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/;
if (ccPattern.test(content) || ssnPattern.test(content)) {
return {
passed: false,
message:
'Your response contains PII (credit card or SSN). ' +
'Redact all card numbers and SSNs before responding.',
};
}
return { passed: true };
},
{
name: 'no_pii',
position: 'output',
onFail: 'retry',
},
);
// ── Agent ─────────────────────────────────────────────────
export const agent = new Agent({
name: 'support_agent',
model: llmModel,
tools: [getOrderStatus, getCustomerInfo],
instructions:
'You are a customer support assistant. Use the available tools to ' +
'answer questions about orders and customers. Always include all ' +
'details from the tool results in your response.',
// ^^^ This instruction deliberately encourages the agent to include
// raw tool output, which will trigger the guardrail on the second
// tool call's PII data.
guardrails: [
piiBlocker.toGuardrailDef(),
sensitiveDataChecker.toGuardrailDef(),
noPii,
],
});
// ── Run ───────────────────────────────────────────────────
async function main() {
const runtime = new AgentRuntime();
try {
// This prompt triggers both tools:
// 1. get_order_status("ORD-42") → safe data, passes guardrail
// 2. get_customer_info("CUST-7") → contains credit card, trips guardrail
const result = await runtime.run(
agent,
'I need a full summary: What\'s the status of order ORD-42, ' +
'and what\'s the profile for customer CUST-7?',
);
result.printResult();
if (result.status !== 'COMPLETED') {
console.error(`\nFAIL: agent run ended ${result.status}: ${result.error ?? ''}`);
process.exitCode = 1;
return;
}
// Verify the guardrail worked — no raw card number in the output
// (stringify: result.output is an object, String() gives "[object Object]")
if (result.output && JSON.stringify(result.output).includes('4532-0150-1234-5678')) {
console.log('[WARN] PII leaked through the guardrail!');
process.exitCode = 1;
} else {
console.log('[OK] PII was redacted from the final output.');
}
// Production pattern:
// 1. Deploy once during CI/CD (optional -- serve() below also deploys):
// await runtime.deploy(agent);
// CLI alternative:
// conductor deploy --package examples/agents --agents support_agent
//
// 2. In a separate long-lived worker process (deploys + registers workers + starts polling):
// await runtime.serve(agent);
} finally {
await runtime.shutdown();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});