- Posted on
- • Artificial Intelligence
Artificial Intelligence Customer Support Workflows
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Build AI-Powered Customer Support Workflows from Bash
Tickets piling up? SLAs slipping? You don’t need a full-blown microservice or an expensive platform to start using AI for customer support. With a few Unix tools and a reliable AI API, you can triage, summarize, and draft replies straight from Bash. This post shows you how to assemble a practical, auditable, and automatable workflow using curl, jq, and sqlite3.
Why this matters:
Faster, more consistent triage and routing
Summaries agents can trust and verify
Draft responses that save minutes per ticket
All in CLI pipelines you can schedule, log, and version-control
What you’ll build
- A portable Bash toolkit to:
- Classify and prioritize tickets (intent + severity)
- Summarize conversations for instant context
- Generate compliant reply drafts for agent review
- Auto-respond to simple FAQs with safe-guards
All examples assume an OpenAI-compatible API (any provider that implements /v1/chat/completions). You control where data goes by choosing the endpoint.
Prerequisites (Linux)
You’ll need curl, jq, and sqlite3. Install with your distro’s package manager:
Debian/Ubuntu (apt):
sudo apt update sudo apt install -y curl jq sqlite3Fedora/RHEL/CentOS (dnf):
sudo dnf install -y curl jq sqliteopenSUSE/SLE (zypper):
sudo zypper refresh sudo zypper install -y curl jq sqlite3
Set API configuration via environment variables (for any OpenAI-compatible service):
export OPENAI_API_KEY="your_api_key_here"
export BASE_URL="https://api.openai.com" # or your compatible endpoint
export MODEL="gpt-4o-mini" # any available model slug
Optional: put these in a .env and source .env before running scripts.
Core shell: AI request helper
Create ai.sh to standardize calls and make parsing easy.
#!/usr/bin/env bash
set -euo pipefail
: "${OPENAI_API_KEY:?Set OPENAI_API_KEY}"
BASE_URL="${BASE_URL:-https://api.openai.com}"
MODEL="${MODEL:-gpt-4o-mini}"
TEMPERATURE="${TEMPERATURE:-0.2}"
# ai_json: send system+user prompts and expect JSON output
ai_json() {
local system="$1"
local user="$2"
curl -sS "${BASE_URL}/v1/chat/completions" \
-H "Authorization: Bearer ${OPENAI_API_KEY}" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg m "$MODEL" \
--arg s "$system" \
--arg u "$user" \
--argjson t "$TEMPERATURE" \
'{
model: $m,
temperature: $t,
response_format: { type: "json_object" },
messages: [
{role:"system", content:$s},
{role:"user", content:$u}
]
}')" \
| jq -r '.choices[0].message.content'
}
Make it executable:
chmod +x ai.sh
Step 1 — Create a tiny ticket store (SQLite)
We’ll keep structured metadata in SQLite so you can query, automate, and audit changes.
#!/usr/bin/env bash
set -euo pipefail
DB="${DB:-support.db}"
sqlite3 "$DB" <<'SQL'
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY,
subject TEXT NOT NULL,
body TEXT NOT NULL,
intent TEXT,
priority TEXT,
sentiment TEXT,
status TEXT DEFAULT 'new',
last_action TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status);
SQL
Save as init_db.sh and run:
./init_db.sh
Insert a sample ticket:
sqlite3 support.db \
"INSERT INTO tickets(subject, body) VALUES('Login issue', 'I can’t reset my password and the reset link never arrives. My email is alice@example.com');"
Step 2 — Redact PII before sending to AI
Minimize exposure by scrubbing common PII. This example replaces emails and phone numbers. Tweak as needed.
#!/usr/bin/env bash
set -euo pipefail
redact() {
sed -E \
-e 's/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/[REDACTED_EMAIL]/g' \
-e 's/\+?[0-9][0-9 .-]{7,}[0-9]/[REDACTED_PHONE]/g'
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
cat | redact
fi
Save as redact.sh and make executable:
chmod +x redact.sh
Step 3 — Triage: intent + priority classification
Classify each ticket into an intent and priority so you can route or queue intelligently.
#!/usr/bin/env bash
set -euo pipefail
. ./ai.sh
DB="${DB:-support.db}"
SYSTEM="You are a support triage assistant. Classify customer tickets.
- intents: password_reset, billing, bug_report, how_to, cancellation, other
- priority: low, medium, high, urgent
Rules:
- Be conservative with 'urgent' unless service is down or security risk.
- Output ONLY JSON with keys: intent, priority."
triage_ticket() {
local subject="$1"
local body="$2"
local redacted
redacted="$(printf '%s\n\n%s\n' "$subject" "$body" | ./redact.sh)"
local user="Subject: ${subject}
Body:
${redacted}"
ai_json "$SYSTEM" "$user"
}
# Process the latest 'new' ticket
row="$(sqlite3 "$DB" -json "SELECT id, subject, body FROM tickets WHERE status='new' ORDER BY id ASC LIMIT 1;")"
[[ -z "$row" || "$row" = "[]" ]] && { echo "No new tickets."; exit 0; }
id="$(echo "$row" | jq -r '.[0].id')"
subject="$(echo "$row" | jq -r '.[0].subject')"
body="$(echo "$row" | jq -r '.[0].body')"
result="$(triage_ticket "$subject" "$body")"
intent="$(echo "$result" | jq -r '.intent')"
priority="$(echo "$result" | jq -r '.priority')"
sqlite3 "$DB" <<SQL
UPDATE tickets
SET intent = '$intent',
priority = '$priority',
status = 'triaged',
last_action = 'triage',
updated_at = CURRENT_TIMESTAMP
WHERE id = $id;
SQL
echo "Ticket #$id triaged: intent=$intent priority=$priority"
Save as triage.sh, then:
chmod +x triage.sh
./triage.sh
Step 4 — Summarize for agents (context + sentiment)
Give agents a fast, consistent brief they can trust.
#!/usr/bin/env bash
set -euo pipefail
. ./ai.sh
DB="${DB:-support.db}"
SYSTEM="You are a support summarizer. Produce:
- tldr: one-sentence summary
- sentiment: positive, neutral, negative
- key_points: 3 bullet points
Output JSON with keys: tldr, sentiment, key_points (array)."
row="$(sqlite3 "$DB" -json "SELECT id, subject, body FROM tickets WHERE status='triaged' ORDER BY updated_at DESC LIMIT 1;")"
[[ -z "$row" || "$row" = "[]" ]] && { echo "No triaged tickets."; exit 0; }
id="$(echo "$row" | jq -r '.[0].id')"
subject="$(echo "$row" | jq -r '.[0].subject')"
body="$(echo "$row" | jq -r '.[0].body')"
redacted="$(printf '%s\n\n%s\n' "$subject" "$body" | ./redact.sh)"
user="Subject: ${subject}
Body:
${redacted}
"
result="$(ai_json "$SYSTEM" "$user")"
tldr="$(echo "$result" | jq -r '.tldr')"
sentiment="$(echo "$result" | jq -r '.sentiment')"
sqlite3 "$DB" <<SQL
UPDATE tickets
SET sentiment = '$sentiment',
last_action = 'summarize',
updated_at = CURRENT_TIMESTAMP
WHERE id = $id;
SQL
echo "Summary for ticket #$id:"
echo "$result" | jq .
Save as summarize.sh and run:
chmod +x summarize.sh
./summarize.sh
Step 5 — Draft replies with a style guide (human-in-the-loop)
Generate a safe, compliant draft that an agent can review and edit before sending.
#!/usr/bin/env bash
set -euo pipefail
. ./ai.sh
DB="${DB:-support.db}"
EDITOR="${EDITOR:-vi}"
STYLE_GUIDE="Write in a friendly, concise tone.
- Never promise refunds or SLAs.
- If unsure, ask a clarifying question.
- Provide exact, numbered steps if procedural.
- Keep to 150–200 words unless complex.
- Do not include PII; use placeholders."
SYSTEM="You are a support agent drafting replies. Follow the style guide strictly. Output plain text."
row="$(sqlite3 "$DB" -json "SELECT id, subject, body, intent FROM tickets WHERE status='triaged' ORDER BY priority DESC, updated_at ASC LIMIT 1;")"
[[ -z "$row" || "$row" = "[]" ]] && { echo "No tickets ready for draft."; exit 0; }
id="$(echo "$row" | jq -r '.[0].id')"
subject="$(echo "$row" | jq -r '.[0].subject')"
body="$(echo "$row" | jq -r '.[0].body')"
intent="$(echo "$row" | jq -r '.[0].intent')"
redacted="$(printf '%s\n\n%s\n' "$subject" "$body" | ./redact.sh)"
user="Subject: ${subject}
Intent: ${intent}
Customer message (PII-redacted):
${redacted}
Draft a helpful reply. Include steps if relevant. End with an open question to confirm resolution."
draft="$(curl -sS "${BASE_URL}/v1/chat/completions" \
-H "Authorization: Bearer ${OPENAI_API_KEY}" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg m "${MODEL:-gpt-4o-mini}" --arg s "$SYSTEM" --arg g "$STYLE_GUIDE" --arg u "$user" '{
model: $m, temperature: 0.4,
messages: [
{role:"system", content:$s},
{role:"system", content:("Style Guide:\n" + $g)},
{role:"user", content:$u}
]
}')" \
| jq -r '.choices[0].message.content')"
tmp="$(mktemp)"
printf "%s\n" "$draft" > "$tmp"
${EDITOR} "$tmp"
final="$(cat "$tmp")"
rm -f "$tmp"
printf "\n--- Final Draft ---\n%s\n" "$final"
sqlite3 "$DB" <<SQL
UPDATE tickets
SET status = 'drafted',
last_action = 'draft_reply',
updated_at = CURRENT_TIMESTAMP
WHERE id = $id;
SQL
echo "Draft saved for ticket #$id."
Save as draft_reply.sh, then:
chmod +x draft_reply.sh
./draft_reply.sh
You now have a repeatable human-in-the-loop drafting flow that respects a style guide and prevents risky promises.
Bonus — Safe auto-responses for common FAQs
If intent is password_reset, you might automate a polite, personalized response. Keep it simple and templated; only use AI to tailor tone if you’re comfortable.
#!/usr/bin/env bash
set -euo pipefail
DB="${DB:-support.db}"
row="$(sqlite3 "$DB" -json "SELECT id, subject FROM tickets WHERE status='triaged' AND intent='password_reset' ORDER BY updated_at ASC LIMIT 1;")"
[[ -z "$row" || "$row" = "[]" ]] && { echo "No password reset tickets."; exit 0; }
id="$(echo "$row" | jq -r '.[0].id')"
subject="$(echo "$row" | jq -r '.[0].subject')"
response="Hi there,
Thanks for reaching out about password access.
Please try these steps:
1) Visit the Password Reset page and submit your account email.
2) Check your spam/junk folder for the reset link.
3) If you don’t receive a link within 10 minutes, reply here and we’ll assist further.
Best regards,
Support Team"
printf "\n--- Auto-response for ticket #%s (%s) ---\n%s\n" "$id" "$subject" "$response"
sqlite3 "$DB" <<SQL
UPDATE tickets
SET status = 'awaiting_customer',
last_action = 'auto_respond_password_reset',
updated_at = CURRENT_TIMESTAMP
WHERE id = $id;
SQL
Save as auto_respond.sh and run:
chmod +x auto_respond.sh
./auto_respond.sh
You can later swap the static template with an AI-personalized version that still adheres to your style guide and guardrails.
Why this approach works
Bash-native: Compose, log, and schedule with cron or systemd; integrate with existing scripts.
Observable: Every step writes to SQLite; you can audit who did what, when.
Privacy-aware: Redaction before API calls. You can add more deterministic filters.
Portable:
curl,jq,sqlite3are available across major distros.Vendor-flexible: Works with any OpenAI-compatible endpoint by changing
BASE_URLand model.
Real-world tips
Rate-limiting: Add
sleepor a token-bucket when looping over many tickets.Backoff on errors: Wrap
curlwith retry logic (e.g.,--retry 5 --retry-delay 2).System prompts as config: Store style guides and rules in version-controlled files.
Guardrails: For auto-actions, prefer templates + minimal AI; always log final content.
Access control: Keep your API key in a restricted
.envand never commit it.
Next steps (CTA)
Wire these scripts into your helpdesk API to ingest new tickets and post replies.
Add a
systemdtimer to runtriage.shandsummarize.shevery few minutes.Expand intents and templates; implement escalation rules by priority.
Add tests with
shellcheckand sample fixtures to keep prompts stable as you iterate.
If you want a minimal, auditable, and automation-friendly way to bring AI into customer support, start with Bash. The examples above are enough to pilot real improvements this week.