Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:09 +08:00
commit 9475095985
30 changed files with 5609 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
/**
* Human-in-the-Loop (HITL) Pattern
*
* Demonstrates:
* - Requiring human approval for tools
* - Handling interruptions
* - Approving/rejecting tool calls
* - Serializing and resuming state
*/
import { z } from 'zod';
import {
Agent,
Runner,
tool,
ToolApprovalItem,
} from '@openai/agents';
import * as readline from 'readline';
// ========================================
// Tools Requiring Approval
// ========================================
const sendEmailTool = tool({
name: 'send_email',
description: 'Send an email to a recipient',
parameters: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
execute: async ({ to, subject, body }) => {
console.log('\n📧 Email sent!');
return `Email sent to ${to} with subject "${subject}"`;
},
requiresApproval: true, // Require human approval
});
const processRefundTool = tool({
name: 'process_refund',
description: 'Process a refund for a customer',
parameters: z.object({
customerId: z.string(),
amount: z.number(),
reason: z.string(),
}),
execute: async ({ customerId, amount, reason }) => {
console.log('\n💰 Refund processed!');
return `Refunded $${amount} to customer ${customerId}. Reason: ${reason}`;
},
requiresApproval: true,
});
const deleteAccountTool = tool({
name: 'delete_account',
description: 'Permanently delete a user account',
parameters: z.object({
userId: z.string(),
confirmation: z.string(),
}),
execute: async ({ userId }) => {
console.log('\n🗑 Account deleted!');
return `Account ${userId} has been permanently deleted`;
},
requiresApproval: true,
});
// ========================================
// Agent with Approval-Required Tools
// ========================================
const customerServiceAgent = new Agent({
name: 'Customer Service Agent',
instructions: 'You help customers with their requests. Use tools when necessary but they will require human approval.',
tools: [sendEmailTool, processRefundTool, deleteAccountTool],
});
// ========================================
// Helper: Prompt User for Approval
// ========================================
async function promptUserForApproval(
toolName: string,
args: Record<string, any>
): Promise<boolean> {
console.log('\n' + '='.repeat(60));
console.log('⚠️ APPROVAL REQUIRED');
console.log('='.repeat(60));
console.log('Tool:', toolName);
console.log('Arguments:', JSON.stringify(args, null, 2));
console.log('='.repeat(60));
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise(resolve => {
rl.question('Approve this action? (y/n): ', answer => {
rl.close();
resolve(answer.toLowerCase() === 'y');
});
});
}
// ========================================
// Run Agent with Human-in-the-Loop
// ========================================
async function runWithApproval(input: string) {
console.log('\n🤖 Running agent with human approval...\n');
console.log('User:', input);
const runner = new Runner(customerServiceAgent);
let result = await runner.run(input);
// Handle interruptions (approval requests)
while (result.interruption) {
if (result.interruption.type === 'tool_approval') {
const approvalItem = result.interruption as ToolApprovalItem;
console.log(`\n🛑 Agent wants to call: ${approvalItem.toolCall.name}`);
// Ask user for approval
const approved = await promptUserForApproval(
approvalItem.toolCall.name,
approvalItem.toolCall.arguments
);
if (approved) {
console.log('\n✅ Approved - resuming agent...');
result = await result.state.approve(approvalItem);
} else {
console.log('\n❌ Rejected - agent will find alternative...');
result = await result.state.reject(approvalItem, {
reason: 'User rejected the action',
});
}
} else {
// Handle other interruption types if needed
console.log('Unexpected interruption type:', result.interruption.type);
break;
}
}
console.log('\n✅ Agent finished');
console.log('Final output:', result.output);
return result;
}
// ========================================
// Example: State Serialization
// ========================================
async function exampleStateSerialization(input: string) {
console.log('\n🔄 Example: Serializing and Resuming State\n');
const runner = new Runner(customerServiceAgent);
let result = await runner.run(input);
if (result.interruption?.type === 'tool_approval') {
const approvalItem = result.interruption as ToolApprovalItem;
console.log('\n💾 Saving state for later...');
// Serialize state (e.g., save to database)
const serializedState = JSON.stringify(result.state);
console.log('State saved (length:', serializedState.length, 'chars)');
// Simulate delay (user goes away and comes back later)
console.log('\n⏳ User away...\n');
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('👤 User returned!\n');
// Deserialize state
// Note: In real implementation, you'd use RunState.fromString()
// const restoredState = RunState.fromString(customerServiceAgent, serializedState);
// For demo, we'll just approve from current state
const approved = await promptUserForApproval(
approvalItem.toolCall.name,
approvalItem.toolCall.arguments
);
if (approved) {
result = await result.state.approve(approvalItem);
} else {
result = await result.state.reject(approvalItem);
}
}
console.log('\n✅ Final output:', result.output);
}
// ========================================
// Example Usage
// ========================================
async function main() {
const examples = [
'Send an email to customer@example.com saying their order has shipped',
'Process a $50 refund for customer ABC123 due to defective product',
'Delete account user-456 permanently',
];
// Interactive mode (uncomment to run)
// for (const input of examples) {
// await runWithApproval(input);
// console.log('\n' + '='.repeat(80) + '\n');
// }
// State serialization example
// await exampleStateSerialization(examples[1]);
console.log('\n💡 Uncomment the code above to run interactive approval demos\n');
}
main();
export {
customerServiceAgent,
sendEmailTool,
processRefundTool,
deleteAccountTool,
runWithApproval,
};