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 operatorsWhy: 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.Input/Output Contract
| Input | Type | Required | Description |
|---|---|---|---|
ticket_id | string | Yes | Support ticket identifier |
message | string | Yes | Customer message to respond to |
tone | string | No | Response tone (default: professional) |
| Output | Type | Description |
|---|---|---|
reply | string | Generated reply draft |
confidence | number | Confidence score (0-1) |
ok | boolean | Success indicator |
Prerequisites
Copy
export OPENAI_API_KEY=your_key_here
pip install praisonaiagents
Step-by-Step Build
1
Create Recipe Directory
Copy
mkdir -p ~/.praison/templates/customer-support-reply-drafter
cd ~/.praison/templates/customer-support-reply-drafter
2
Create TEMPLATE.yaml
Copy
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
Copy
# recipe.py
from praisonaiagents import Agent, Task, PraisonAIAgents
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 = PraisonAIAgents(
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
Copy
# 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
Copy
# 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
- Model 2: CLI Invocation
- Model 3: Plugin Mode
- Model 4: Local HTTP Sidecar
- Model 5: Remote Managed Runner
- Model 6: Event-Driven
Copy
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"])
Safety: Customer messages may contain PII. Handle securely.
Copy
praison recipes run customer-support-reply-drafter \
--input '{"ticket_id": "T-123", "message": "Help!"}' \
--json | jq '.reply'
Copy
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}
)
Copy
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'
})
});
Copy
response = requests.post(
"https://api.support-tools.com/draft-reply",
headers={"Authorization": f"Bearer {api_key}"},
json={"ticket_id": "T-123", "message": "Help needed"}
)
Copy
def on_new_ticket(event):
queue.send({
"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
Low confidence scores
Complex or ambiguous messages may result in lower confidence. Review these manually.
Tone doesn't match
Tone doesn't match
Ensure the tone parameter matches your brand guidelines. Customize instructions if needed.
Next Steps
- Meeting Minutes Action Items - Extract action items from support calls
- Document Summarizer - Summarize support documentation

