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.
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
export OPENAI_API_KEY=your_key_here
pip install praisonaiagentsStep-by-Step Build
- Create Recipe Directory
bash mkdir -p ~/.praisonai/templates/customer-support-reply-drafter cd ~/.praisonai/templates/customer-support-reply-drafter - 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 - 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 - 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
# 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
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
praison recipes run customer-support-reply-drafter \
--input '{"ticket_id": "T-123", "message": "Help!"}' \
--json | jq '.reply'Model 3: Plugin Mode
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
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
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
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
- Meeting Minutes Action Items – Extract action items from support calls
- Document Summarizer – Summarize support documentation