Customer Support Reply Drafter

Draft professional support replies with configurable tone and confidence scoring

Customer Support Reply Drafter

Generate professional customer support replies with tone control and confidence scoring.

Problem Statement

Who: Support teams, customer service managers, help desk operators

Why: Consistent, high-quality replies improve customer satisfaction and reduce response time.

What You’ll Build

A recipe that analyzes customer messages and drafts appropriate, professional replies.

Diagram

📧 Customer Message

Analyze Intent

Draft Reply

Confidence Score

📝 Reply + Score

Input/Output Contract

InputTypeRequiredDescription
ticket_idstringYesSupport ticket identifier
messagestringYesCustomer message to respond to
tonestringNoResponse tone (default: professional)
OutputTypeDescription
replystringGenerated reply draft
confidencenumberConfidence score (0-1)
okbooleanSuccess indicator

Prerequisites

bash
export OPENAI_API_KEY=your_key_here
pip install praisonaiagents

Step-by-Step Build

  1. Create Recipe Directory
    bash
    mkdir -p ~/.praisonai/templates/customer-support-reply-drafter
    cd ~/.praisonai/templates/customer-support-reply-drafter
  2. Create TEMPLATE.yaml
    yaml
    name: customer-support-reply-drafter
    version: "1.0.0"
    description: "Generate professional support reply drafts"
    author: "PraisonAI"
    license: "MIT"
    
    tags:
      - support
      - customer-service
      - text-generation
    
    requires:
      env:
        - OPENAI_API_KEY
      packages:
        - praisonaiagents
    
    inputs:
      ticket_id:
        type: string
        description: "Support ticket identifier"
        required: true
      message:
        type: string
        description: "Customer message to respond to"
        required: true
      tone:
        type: string
        description: "Response tone"
        default: "professional"
        enum:
          - professional
          - friendly
          - formal
    
    outputs:
      reply:
        type: string
        description: "Generated reply draft"
      confidence:
        type: number
        description: "Confidence score (0-1)"
    
    cli:
      command: "praison recipes run customer-support-reply-drafter"
      examples:
        - 'praison recipes run customer-support-reply-drafter --input ''{"ticket_id": "T-123", "message": "I need help"}'''
    
    safety:
      dry_run_default: false
      requires_consent: false
      overwrites_files: false
      network_access: true
      pii_handling: true
  3. Create recipe.py
    python
    # recipe.py
    from praisonaiagents import Agent, Task, AgentTeam
    
    def run(input_data: dict, config: dict = None) -> dict:
        """Generate a support reply draft."""
        ticket_id = input_data.get("ticket_id")
        message = input_data.get("message")
        tone = input_data.get("tone", "professional")
    
        if not ticket_id:
            return {"ok": False, "error": {"code": "MISSING_INPUT", "message": "ticket_id is required"}}
    
        if not message:
            return {"ok": False, "error": {"code": "MISSING_INPUT", "message": "message is required"}}
    
        try:
            tone_guidelines = {
                "professional": "Clear, courteous, and business-appropriate",
                "friendly": "Warm, approachable, and conversational",
                "formal": "Polished, respectful, and traditional"
            }
    
            # Create support agent
            support_agent = Agent(
                name="Support Specialist",
                role="Customer Support Expert",
                goal=f"Draft a {tone} reply to customer inquiries",
                instructions=f"""
                You are an expert customer support specialist.
                Tone: {tone} - {tone_guidelines[tone]}
    
                Guidelines:
                - Be helpful and empathetic
                - Address the customer's concern directly
                - Provide clear next steps if applicable
                - Keep responses concise but complete
                - Never make promises you can't keep
                - Acknowledge the customer's frustration if present
                """,
            )
    
            # Create quality checker
            quality_agent = Agent(
                name="Quality Reviewer",
                role="Support Quality Analyst",
                goal="Ensure reply quality and assign confidence",
                instructions="""
                You are a support quality analyst.
                - Check for completeness
                - Verify tone consistency
                - Ensure no inappropriate content
                - Assign confidence score (0-1)
                - Flag any concerns
                """,
            )
    
            # Define tasks
            draft_task = Task(
                name="draft_reply",
                description=f"""
                Draft a {tone} reply to this customer message:
    
                Ticket: {ticket_id}
                Message: {message}
    
                Provide a professional, helpful response.
                """,
                expected_output="A well-crafted support reply",
                agent=support_agent,
            )
    
            review_task = Task(
                name="review_reply",
                description="""
                Review the draft reply for quality.
                Assign a confidence score from 0 to 1 where:
                - 0.9-1.0: Excellent, ready to send
                - 0.7-0.9: Good, minor review recommended
                - 0.5-0.7: Acceptable, review before sending
                - Below 0.5: Needs revision
    
                Output format: CONFIDENCE: X.XX
                """,
                expected_output="Confidence score and any notes",
                agent=quality_agent,
                context=[draft_task],
            )
    
            # Execute
            agents = AgentTeam(
                agents=[support_agent, quality_agent],
                tasks=[draft_task, review_task],
            )
    
            result = agents.start()
    
            # Parse confidence
            review_text = result.get("review_reply", "")
            confidence = parse_confidence(review_text)
    
            return {
                "ok": True,
                "reply": result.get("draft_reply", ""),
                "confidence": confidence,
                "artifacts": [],
                "warnings": [] if confidence >= 0.7 else ["Low confidence - review before sending"],
            }
    
        except Exception as e:
            return {"ok": False, "error": {"code": "PROCESSING_ERROR", "message": str(e)}}
    
    
    def parse_confidence(text: str) -> float:
        """Extract confidence score from review text."""
        import re
        match = re.search(r'CONFIDENCE:\s*([\d.]+)', text, re.IGNORECASE)
        if match:
            try:
                return min(1.0, max(0.0, float(match.group(1))))
            except ValueError:
                pass
    
        # Default confidence based on text analysis
        if "excellent" in text.lower() or "ready" in text.lower():
            return 0.9
        elif "good" in text.lower():
            return 0.8
        elif "acceptable" in text.lower():
            return 0.6
        return 0.7
  4. Create test_recipe.py
    python
    # test_recipe.py
    import pytest
    from recipe import run, parse_confidence
    
    def test_missing_ticket_id():
        result = run({"message": "Help me"})
        assert result["ok"] is False
        assert result["error"]["code"] == "MISSING_INPUT"
    
    def test_missing_message():
        result = run({"ticket_id": "T-123"})
        assert result["ok"] is False
        assert result["error"]["code"] == "MISSING_INPUT"
    
    def test_parse_confidence():
        assert parse_confidence("CONFIDENCE: 0.85") == 0.85
        assert parse_confidence("The reply is excellent") == 0.9
        assert parse_confidence("This is good") == 0.8
    
    def test_confidence_bounds():
        assert parse_confidence("CONFIDENCE: 1.5") == 1.0
        assert parse_confidence("CONFIDENCE: -0.5") == 0.0
    
    @pytest.mark.integration
    def test_end_to_end():
        result = run({
            "ticket_id": "T-123",
            "message": "I can't log into my account",
            "tone": "professional"
        })
    
        assert result["ok"] is True
        assert len(result["reply"]) > 50
        assert 0 <= result["confidence"] <= 1
    
    @pytest.mark.integration
    def test_different_tones():
        tones = ["professional", "friendly", "formal"]
        for tone in tones:
            result = run({
                "ticket_id": "T-124",
                "message": "How do I reset my password?",
                "tone": tone
            })
            assert result["ok"] is True

Run Locally

bash
# Basic usage
praison recipes run customer-support-reply-drafter \
  --input '{"ticket_id": "T-123", "message": "My order hasnt arrived"}'

# With tone
praison recipes run customer-support-reply-drafter \
  --input '{"ticket_id": "T-456", "message": "This is unacceptable!", "tone": "formal"}'

Deploy & Integrate: 6 Integration Models

Model 1: Embedded SDK

python
from praisonai import recipe

result = recipe.run(
    "customer-support-reply-drafter",
    input={
        "ticket_id": "T-123",
        "message": "I need a refund",
        "tone": "professional"
    }
)

if result.ok:
    if result.output["confidence"] >= 0.8:
        send_reply(result.output["reply"])
    else:
        queue_for_review(result.output["reply"])

Model 2: CLI Invocation

bash
praison recipes run customer-support-reply-drafter \
  --input '{"ticket_id": "T-123", "message": "Help!"}' \
  --json | jq '.reply'

Model 3: Plugin Mode

python
class SupportPlugin:
    def draft_reply(self, ticket_id, message, tone="professional"):
        from praisonai import recipe
        return recipe.run(
            "customer-support-reply-drafter",
            input={"ticket_id": ticket_id, "message": message, "tone": tone}
        )

Model 4: Local HTTP Sidecar

javascript
const response = await fetch('http://localhost:8765/recipes/customer-support-reply-drafter/run', {
  method: 'POST',
  body: JSON.stringify({
    ticket_id: 'T-123',
    message: 'I need help',
    tone: 'friendly'
  })
});

Model 5: Remote Managed Runner

python
response = requests.post(
    "https://api.support-tools.com/draft-reply",
    headers={"Authorization": f"Bearer {api_key}"},
    json={"ticket_id": "T-123", "message": "Help needed"}
)

Model 6: Event-Driven

python
def on_new_ticket(event):
    import queue as q
    job_queue = q.Queue()  # Replace with SQS/RabbitMQ in production
    job_queue.put({
        "recipe": "customer-support-reply-drafter",
        "input": {
            "ticket_id": event['ticket_id'],
            "message": event['message']
        },
        "callback_url": f"https://api.example.com/tickets/{event['ticket_id']}/draft"
    })

Troubleshooting

Low confidence scores

Complex or ambiguous messages may result in lower confidence. Review these manually.

Tone doesn't match

Ensure the tone parameter matches your brand guidelines. Customize instructions if needed.

Next Steps