Minded docs
  • Introduction
  • Getting Started
    • Installation
    • Environment Configuration
    • Project Configuration
    • Quick Start
  • Low-Code Editor
    • Flows
    • Nodes
    • Edges
    • Tools
    • Triggers
    • Playbooks
  • SDK
    • Agent API
    • Debugging
    • Memory
    • Events
    • Logging
    • Parallel LLM Requests
  • Platform
    • Secrets
    • PII Masking
    • Operator
    • Browser Task
    • SSO (Single Sign-On)
  • Tooling
    • Timers
    • Document Processing
    • Data Extraction
    • Classifier
  • Integrations
    • Zendesk
  • Examples
    • Order Refund Flow
  • Resources
    • Your First Eval
Powered by GitBook
On this page
  1. Examples

Order Refund Flow

PreviousZendeskNextYour First Eval

Last updated 13 days ago

CtrlK
  • Overview
  • Complete Implementation
  • Project Structure
  • 1. Agent Configuration
  • 2. Memory Schema
  • 3. Flow Definition
  • 4. Tools Implementation
  • 5. Main Agent Implementation
  • Running the Example
  • Expected Output
  • Key Features Demonstrated

A complete example demonstrating how to build an intelligent customer support agent that can handle order refunds with sophisticated routing and business logic.

Overview

This example showcases:

  • Trigger handling with qualification logic

  • Prompt-based routing for intelligent conversation flow

  • Tool integration for business logic execution

  • Memory management for maintaining conversation state

  • Error handling and edge cases

Complete Implementation

Project Structure

order-refund-agent/
ā”œā”€ā”€ minded.json          # Agent configuration
ā”œā”€ā”€ schema.ts           # Memory type definition
ā”œā”€ā”€ agent.ts            # Main agent implementation
ā”œā”€ā”€ flows/
│   └── refundFlow.yaml # Flow definition
└── tools/
    ā”œā”€ā”€ lookupOrder.ts  # Order lookup tool
    ā”œā”€ā”€ processRefund.ts # Refund processing tool
    └── index.ts        # Tool exports

1. Agent Configuration

Create your minded.json configuration file (see Project Configuration for detailed documentation):

minded.json:

{
  "flows": ["./flows"],
  "agent": "./agent.ts",
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7
    }
  }
}

2. Memory Schema

schema.ts:

import { z } from 'zod';

const memorySchema = z.object({
  // Customer information
  customerName: z.string().optional(),
  customerEmail: z.string().optional(),
  customerId: z.string().optional(),

  // Order information
  orderId: z.string().optional(),
  orderInfo: z
    .object({
      id: z.string(),
      status: z.string(),
      total: z.number(),
      items: z.array(z.string()),
      purchaseDate: z.string(),
      canRefund: z.boolean(),
    })
    .optional(),

  // Conversation state
  conversationStage: z.enum(['greeting', 'gathering_info', 'processing_refund', 'complete']).optional(),

  // Refund information
  refundReason: z.string().optional(),
  refundAmount: z.number().optional(),
  refundProcessed: z.boolean().optional(),

  // Metadata
  timestamp: z.string().optional(),
  customerQuery: z.string().optional(),
});

export type Memory = z.infer<typeof memorySchema>;
export default memorySchema;

3. Flow Definition

flows/refundFlow.yaml:

name: 'Advanced Order Refund Flow'
nodes:
  # Entry point
  - type: trigger
    triggerType: manual
    name: Customer Support Trigger

  # Initial customer interaction
  - type: promptNode
    name: Support Agent
    prompt: |
      You are a helpful customer support agent specializing in order issues and refunds.

      Current customer context:
      - Query: {{memory.customerQuery}}
      - Stage: {{memory.conversationStage}}

      If this is the first interaction:
      1. Greet the customer warmly
      2. Ask for their order ID if they mention order issues
      3. Ask for their name and email for verification

      If you have order information:
      - Customer: {{memory.customerName}}
      - Order ID: {{memory.orderId}}
      - Order Status: {{memory.orderInfo.status}}
      - Order Total: ${{memory.orderInfo.total}}

      Be empathetic, professional, and focused on resolving their issue.
    llmConfig:
      name: ChatOpenAI
      properties:
        model: gpt-4o
        temperature: 0.7

  # Order lookup functionality
  - type: tool
    name: Lookup Order
    toolName: lookupOrder

  # Junction for decision making
  - type: junction
    name: Refund Decision Point

  # Refund processing
  - type: tool
    name: Process Refund
    toolName: processRefund

  # Final confirmation
  - type: promptNode
    name: Refund Confirmation
    prompt: |
      Great news! Your refund has been processed successfully.

      Refund Details:
      - Order ID: {{memory.orderId}}
      - Refund Amount: ${{memory.refundAmount}}
      - Reason: {{memory.refundReason}}

      You can expect to see the refund in your original payment method within 3-5 business days.

      Is there anything else I can help you with today?

edges:
  # Initial flow
  - source: Customer Support Trigger
    target: Support Agent
    type: stepForward

  # Route to order lookup when customer provides order ID
  - source: Support Agent
    target: Lookup Order
    type: promptCondition
    prompt: 'Customer has provided an order ID and wants to look up order information'

  # After order lookup, go to decision point
  - source: Lookup Order
    target: Refund Decision Point
    type: stepForward

  # Decision point routing
  - source: Refund Decision Point
    target: Process Refund
    type: logicalCondition
    condition: "({ memory }) => memory.orderInfo?.canRefund && memory.customerQuery?.toLowerCase().includes('refund')"

  - source: Refund Decision Point
    target: Support Agent
    type: logicalCondition
    condition: '({ memory }) => !memory.orderInfo?.canRefund'

  # After refund processing
  - source: Process Refund
    target: Refund Confirmation
    type: stepForward

  # Allow continued conversation
  - source: Refund Confirmation
    target: Support Agent
    type: promptCondition
    prompt: 'Customer has additional questions or needs more help'

4. Tools Implementation

tools/lookupOrder.ts:

import { z } from 'zod';
import { Tool } from 'mindedjs/src/types/Tools.types';
import { Memory } from '../schema';

const schema = z.object({
  orderId: z.string(),
  customerEmail: z.string().optional(),
});

const lookupOrderTool: Tool<typeof schema, Memory> = {
  name: 'lookupOrder',
  description: 'Look up order details by order ID and optionally verify with customer email',
  input: schema,
  execute: async ({ input, memory }) => {
    try {
      // Simulate API call to order system
      const orderInfo = await fetchOrderFromAPI(input.orderId, input.customerEmail);

      if (!orderInfo) {
        throw new Error(`Order ${input.orderId} not found or email doesn't match`);
      }

      // Determine if refund is allowed based on business rules
      const canRefund = determineRefundEligibility(orderInfo);

      return {
        memory: {
          orderId: input.orderId,
          orderInfo: {
            ...orderInfo,
            canRefund,
          },
          conversationStage: 'gathering_info',
          customerId: orderInfo.customerId,
        },
      };
    } catch (err) {
      logger.error({ message: 'Order lookup failed:', err });
      return {
        memory: {
          orderId: input.orderId,
          conversationStage: 'gathering_info',
        },
      };
    }
  },
};

// Simulate order API
async function fetchOrderFromAPI(orderId: string, email?: string) {
  // Simulate database lookup
  const orders = {
    '12345': {
      id: '12345',
      customerId: 'cust_123',
      customerEmail: '[email protected]',
      status: 'delivered',
      total: 89.99,
      items: ['Wireless Headphones', 'Phone Case'],
      purchaseDate: '2024-01-15T10:30:00Z',
    },
    '67890': {
      id: '67890',
      customerId: 'cust_456',
      customerEmail: '[email protected]',
      status: 'shipped',
      total: 199.99,
      items: ['Laptop Stand', 'Wireless Mouse'],
      purchaseDate: '2024-01-20T14:20:00Z',
    },
  };

  const order = orders[orderId];
  if (!order) return null;

  // Verify email if provided
  if (email && order.customerEmail !== email) {
    return null;
  }

  return order;
}

// Business rules for refund eligibility
function determineRefundEligibility(orderInfo: any): boolean {
  const purchaseDate = new Date(orderInfo.purchaseDate);
  const now = new Date();
  const daysSincePurchase = Math.floor((now.getTime() - purchaseDate.getTime()) / (1000 * 60 * 60 * 24));

  // Refund rules:
  // - Must be within 30 days
  // - Order must be delivered
  // - Order total must be less than $500 (higher amounts need manager approval)
  return daysSincePurchase <= 30 && orderInfo.status === 'delivered' && orderInfo.total < 500;
}

export default lookupOrderTool;

tools/processRefund.ts:

import { z } from 'zod';
import { Tool } from 'mindedjs/src/types/Tools.types';
import { Memory } from '../schema';

const schema = z.object({
  orderId: z.string(),
  customerName: z.string(),
  reason: z.string(),
  amount: z.number().optional(), // Optional partial refund amount
});

const processRefundTool: Tool<typeof schema, Memory> = {
  name: 'processRefund',
  description: 'Process a refund for the customer order',
  input: schema,
  execute: async ({ input, memory }) => {
    try {
      // Determine refund amount
      const refundAmount = input.amount || memory.orderInfo?.total || 0;

      // Validate refund amount
      if (refundAmount > (memory.orderInfo?.total || 0)) {
        throw new Error('Refund amount cannot exceed order total');
      }

      // Process the refund (simulate payment processor API)
      const refundResult = await processRefundWithPaymentProcessor({
        orderId: input.orderId,
        amount: refundAmount,
        reason: input.reason,
      });

      // Log the refund for auditing
      await logRefundTransaction({
        orderId: input.orderId,
        customerId: memory.customerId,
        amount: refundAmount,
        reason: input.reason,
        processedBy: 'ai-agent',
        timestamp: new Date().toISOString(),
      });

      console.log(`āœ… Refund processed: Order ${input.orderId}, Amount: $${refundAmount}`);

      return {
        memory: {
          refundAmount,
          refundReason: input.reason,
          refundProcessed: true,
          conversationStage: 'complete',
        },
      };
    } catch (err) {
      logger.error({ message: 'Refund processing failed:', err });

      return {
        memory: {
          refundAmount: 0,
          refundReason: input.reason,
          refundProcessed: false,
          conversationStage: 'gathering_info', // Go back to gathering info
        },
      };
    }
  },
};

// Simulate payment processor API
async function processRefundWithPaymentProcessor(params: any) {
  // Simulate API call delay
  await new Promise((resolve) => setTimeout(resolve, 1000));

  // Simulate occasional failures for testing
  if (Math.random() < 0.1) {
    throw new Error('Payment processor temporarily unavailable');
  }

  return {
    refundId: `ref_${Date.now()}`,
    status: 'processed',
    amount: params.amount,
  };
}

// Simulate audit logging
async function logRefundTransaction(transaction: any) {
  console.log('šŸ“ Logging refund transaction:', transaction);
  // In real implementation, this would write to a database or audit system
}

export default processRefundTool;

tools/index.ts:

import lookupOrderTool from './lookupOrder';
import processRefundTool from './processRefund';

export default [lookupOrderTool, processRefundTool];

5. Main Agent Implementation

agent.ts:

import { Agent } from 'mindedjs/src/agent';
import { HumanMessage } from '@langchain/core/messages';
import { events } from 'mindedjs/src/index';
import memorySchema, { Memory } from './schema';
import tools from './tools';
import config from './minded.json';

// Create the agent
const agent = new Agent<Memory>({
  memorySchema,
  config,
  tools,
});

// Handle AI messages (responses to user)
agent.on(events.AI_MESSAGE, async ({ message, memory }) => {
  console.log('šŸ¤– Agent:', message);
  if (memory.conversationStage) {
    console.log(`   Stage: ${memory.conversationStage}`);
  }
});

// Handle trigger events with qualification logic
agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
  if (triggerName === 'Customer Support Trigger') {
    // Qualify the trigger - only handle customer service related queries
    const lowerQuery = triggerBody.toLowerCase();
    const serviceKeywords = ['order', 'refund', 'help', 'support', 'issue', 'problem'];

    const isServiceQuery = serviceKeywords.some((keyword) => lowerQuery.includes(keyword));

    if (!isServiceQuery) {
      console.log('āŒ Query not qualified for customer service');
      return { isQualified: false }; // Disqualify this trigger
    }

    console.log('āœ… Customer service query qualified');

    // Transform the request and initialize memory
    return {
      isQualified: true,
      memory: {
        customerQuery: triggerBody,
        conversationStage: 'greeting',
        timestamp: new Date().toISOString(),
      },
      messages: [new HumanMessage(triggerBody)],
    };
  }

  return { isQualified: false }; // Unknown trigger
});

// Example usage function
async function runRefundExample() {
  console.log('šŸš€ Starting Order Refund Flow Example\n');

  const examples = [
    {
      description: 'Happy path - valid refund request',
      query: "Hi, I need to return my order #12345. The headphones don't work properly.",
    },
    {
      description: 'Order lookup required',
      query: 'I want to check the status of my recent order and possibly get a refund.',
    },
    {
      description: 'Non-service query (should be disqualified)',
      query: "What's the weather like today?",
    },
  ];

  for (const example of examples) {
    console.log(`\nšŸ“‹ Example: ${example.description}`);
    console.log(`User: ${example.query}\n`);

    try {
      const result = await agent.invoke({
        triggerBody: example.query,
        triggerName: 'Customer Support Trigger',
        sessionId: `session_${Date.now()}`,
      });

      console.log('āœ… Flow completed successfully');
      console.log('Final memory state:', JSON.stringify(result.memory, null, 2));
    } catch (err) {
      logger.error({ message: 'āŒ Flow execution failed:', err });
    }

    console.log('\n' + '─'.repeat(50));
  }
}

// Run the example if this file is executed directly
if (require.main === module) {
  runRefundExample().catch(console.error);
}

export { agent, runRefundExample };

Running the Example

  1. Install dependencies:

npm install @minded-ai/mindedjs langchain
  1. Set up environment:

export OPENAI_API_KEY="your-openai-api-key"
  1. Run the agent:

npx ts-node agent.ts

Expected Output

šŸš€ Starting Order Refund Flow Example

šŸ“‹ Example: Happy path - valid refund request
User: Hi, I need to return my order #12345. The headphones don't work properly.

āœ… Customer service query qualified
šŸ¤– Agent: Hello! I'm sorry to hear you're having trouble with your headphones. I'd be happy to help you with a return. I see you mentioned order #12345. Could you please provide your email address so I can look up your order details?

āœ… Refund processed: Order 12345, Amount: $89.99
šŸ“ Logging refund transaction: {...}
šŸ¤– Agent: Great news! Your refund has been processed successfully...

āœ… Flow completed successfully

Key Features Demonstrated

  1. Intelligent Qualification: The trigger only accepts customer service related queries

  2. Conversation State Management: Memory tracks the conversation stage and relevant information

  3. Business Logic Integration: Tools implement real business rules for refund eligibility

  4. Error Handling: Graceful handling of failed API calls and invalid requests

  5. Audit Trail: All refund transactions are logged for compliance

  6. Type Safety: Full TypeScript type safety throughout the flow

This example provides a solid foundation for building production-ready customer service agents with MindedJS!