Human-in-the-Loop (HITL) Example
Approval workflows for sensitive operations with the PromptKit SDK.
What You’ll Learn
Section titled “What You’ll Learn”- Using
OnToolAsync()for tools requiring approval - Implementing approval check functions
- Handling pending tool calls
- Resolving or rejecting tools
Prerequisites
Section titled “Prerequisites”- Go 1.21+
- OpenAI API key
Running the Example
Section titled “Running the Example”export OPENAI_API_KEY=your-keygo run .Code Overview
Section titled “Code Overview”conv, err := sdk.Open("./hitl.pack.json", "refund_agent")if err != nil { log.Fatal(err)}defer conv.Close()
// Register tool with approval checkconv.OnToolAsync( "process_refund", // Check function - determines if approval needed func(args map[string]any) tools.PendingResult { amount := args["amount"].(float64) if amount > 100 { return tools.PendingResult{ Reason: "high_value_refund", Message: fmt.Sprintf("Refund of $%.2f requires approval", amount), } } return tools.PendingResult{} // Auto-approve }, // Execute function - runs after approval func(args map[string]any) (any, error) { orderID := args["order_id"].(string) return map[string]any{ "status": "processed", "order_id": orderID, }, nil },)
// Send request - tool may require approvalresp, _ := conv.Send(ctx, "Refund $150 for order #12345")
// Check for pending approvalsfor _, pending := range resp.PendingTools() { fmt.Printf("Pending: %s - %s\n", pending.Name, pending.Message)
// Approve or reject if userApproves() { conv.ResolveTool(pending.ID) } else { conv.RejectTool(pending.ID, "Not authorized") }}Pack File Structure
Section titled “Pack File Structure”{ "prompts": { "refund_agent": { "system_template": "You are a customer support agent...", "tools": ["process_refund"] } }, "tools": { "process_refund": { "name": "process_refund", "description": "Process a refund for a customer order", "parameters": { "type": "object", "properties": { "order_id": { "type": "string" }, "amount": { "type": "number" }, "reason": { "type": "string" } }, "required": ["order_id", "amount", "reason"] } } }}OnToolAsync Signature
Section titled “OnToolAsync Signature”conv.OnToolAsync( name string, // Tool name check func(map[string]any) PendingResult, // Check if approval needed execute func(map[string]any) (any, error), // Execute after approval)Check Function
Section titled “Check Function”Returns PendingResult to indicate if approval is needed:
func(args map[string]any) tools.PendingResult { if needsApproval(args) { return tools.PendingResult{ Reason: "policy_violation", Message: "This action requires supervisor approval", } } return tools.PendingResult{} // Empty = auto-approve}Approval Actions
Section titled “Approval Actions”// Approve - executes the toolresult, err := conv.ResolveTool(pendingID)
// Reject - returns rejection to LLMresult, err := conv.RejectTool(pendingID, "Not authorized")Use Cases
Section titled “Use Cases”- Financial transactions - Refunds over threshold
- Data modifications - Delete operations
- External actions - Send emails, API calls
- Sensitive queries - Access personal data
Key Concepts
Section titled “Key Concepts”- Conditional Approval - Check function decides
- Pending State - Tools wait for human decision
- Async Workflow - UI can handle approvals
- Audit Trail - Log all approvals/rejections
Next Steps
Section titled “Next Steps”- Hello Example - Basic conversation
- Streaming Example - Real-time responses
- Tools Example - Basic function calling