-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy path37-fix-guardrail.ts
More file actions
154 lines (140 loc) · 5.08 KB
/
Copy path37-fix-guardrail.ts
File metadata and controls
154 lines (140 loc) · 5.08 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
/**
* 37 - Fix Guardrail (onFail='fix')
*
* Demonstrates onFail="fix": when the guardrail fails, it provides a
* corrected version of the output via fixedOutput. The workflow uses the
* fixed output directly without calling the LLM again.
*
* This is useful when the correction is deterministic (e.g. stripping PII,
* truncating, formatting) -- faster and cheaper than retry since no LLM
* round-trip is needed.
*
* Comparison of onFail modes:
* - 'retry' -- send feedback to LLM and regenerate (best for style issues)
* - 'fix' -- replace output with fixedOutput (best for deterministic fixes)
* - 'raise' -- terminate the workflow with an error
* - 'human' -- pause for human review (see example 32)
*
* Requirements:
* - Conductor server with LLM support
* - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable
* - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable
*/
import { Agent, AgentRuntime, guardrail, tool } from '@io-orkes/conductor-javascript/agents';
import type { GuardrailResult } from '@io-orkes/conductor-javascript/agents';
import { llmModel } from './settings';
// -- Fix guardrail: redact phone numbers -------------------------------------
// Instead of asking the LLM to retry, this guardrail redacts phone
// numbers directly and returns the cleaned output.
const redactPhoneNumbers = guardrail(
(content: string): GuardrailResult => {
const phonePattern = /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
if (phonePattern.test(content)) {
const redacted = content.replace(
/(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
'[PHONE REDACTED]',
);
return {
passed: false,
message: 'Phone numbers detected and redacted.',
fixedOutput: redacted,
};
}
return { passed: true };
},
{
name: 'phone_redactor',
position: 'output',
onFail: 'fix',
},
);
// -- Tool --------------------------------------------------------------------
const getContactInfo = tool(
async (args: { name: string }) => {
const contacts: Record<string, Record<string, string>> = {
alice: {
name: 'Alice Johnson',
email: 'alice@example.com',
phone: '(555) 123-4567',
department: 'Engineering',
},
bob: {
name: 'Bob Smith',
email: 'bob@example.com',
phone: '555-987-6543',
department: 'Marketing',
},
};
const key = args.name.toLowerCase().split(/\s+/)[0];
return contacts[key] ?? { error: `No contact found for '${args.name}'` };
},
{
name: 'get_contact_info',
description: 'Look up contact information for a person.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Name of the person to look up' },
},
required: ['name'],
},
},
);
// -- Agent -------------------------------------------------------------------
export const agent = new Agent({
name: 'directory_agent',
model: llmModel,
tools: [getContactInfo],
instructions:
'You are a company directory assistant. When asked about employees, ' +
'look up their contact info and share everything you find.',
guardrails: [redactPhoneNumbers],
});
async function main() {
const runtime = new AgentRuntime();
try {
// -- Scenario 1: Guardrail TRIGGERS -- contact has phone number
console.log('='.repeat(60));
console.log(' Scenario 1: Contact with phone number (guardrail triggers)');
console.log('='.repeat(60));
const result = await runtime.run(
agent,
"What's Alice Johnson's contact information?",
);
result.printResult();
const output = String(result.output);
if (output.includes('(555) 123-4567') || output.includes('555-123-4567')) {
console.log('[FAIL] Phone number leaked through the guardrail!');
} else if (output.includes('[PHONE REDACTED]')) {
console.log('[OK] Phone number was auto-redacted by fix guardrail');
} else {
console.log('[OK] No phone number in output');
}
// -- Scenario 2: Guardrail does NOT trigger -- no phone in response
console.log('\n' + '='.repeat(60));
console.log(' Scenario 2: General question (guardrail does not trigger)');
console.log('='.repeat(60));
const result2 = await runtime.run(
agent,
'What department does Alice work in? Just the department name.',
);
result2.printResult();
const output2 = String(result2.output);
if (output2.includes('[PHONE REDACTED]')) {
console.log('[WARN] Unexpected redaction in clean response');
} else {
console.log('[OK] No redaction needed -- guardrail passed cleanly');
}
// Production pattern:
// 1. Deploy once during CI/CD (optional -- serve() below also deploys):
// await runtime.deploy(agent);
// CLI alternative:
// agentspan deploy --package sdk/typescript/examples --agents directory_agent
//
// 2. In a separate long-lived worker process (deploys + registers workers + starts polling):
// await runtime.serve(agent);
} finally {
await runtime.shutdown();
}
}
main().catch(console.error);