- Posted on
- • Artificial Intelligence
Artificial Intelligence Bash Design Patterns
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Bash Design Patterns: Make Your Shell Scripts Think
If you’re comfortable piping text between grep, awk, and jq, imagine piping intelligence itself. AI endpoints are now just HTTP calls away. With a few robust Bash design patterns, you can turn brittle one-offs into reliable, testable, and scalable AI-powered command-line tools.
This article shows how to integrate LLMs into your shell scripts using a handful of proven patterns: template-and-version prompts, deterministic caching, robust retries with a circuit breaker, safe batch processing, and auditable logs. You’ll get copy‑pasteable snippets, real-world examples, and package-manager-specific install commands.
Why Bash + AI is a valid combo
Bash is the lingua franca for glue code: it’s already where you watch logs, transform text, and orchestrate jobs.
AI endpoints are HTTP-based:
curl+jqgets you 90% there if you design for reliability.Design patterns prevent the “it worked once on my laptop” problem: they increase reproducibility, control cost, and tame rate limits.
Prerequisites
We’ll use curl (HTTP), jq (JSON), and parallel (optional, for batch work). Install them with your package manager:
Debian/Ubuntu (apt):
sudo apt update sudo apt install -y curl jq parallelFedora/RHEL/CentOS (dnf):
sudo dnf install -y curl jq parallelopenSUSE (zypper):
sudo zypper refresh sudo zypper install -y curl jq parallel
Set your API configuration (example uses an OpenAI-compatible endpoint; adjust as needed):
export OPENAI_API_KEY="sk-..."
export AI_API_BASE="https://api.openai.com" # or your self-hosted/base URL
export AI_MODEL="gpt-4o-mini" # any model your provider supports
Tip: put those exports in ~/.bashrc (or .zshrc) for convenience.
Core patterns (copy-paste friendly)
Below is a compact library of functions you can drop into ai_patterns.sh. It implements:
1) Prompt templates with versioning
2) Deterministic file-based caching
3) Robust requests: timeouts, retries, backoff, and a circuit breaker
4) Safe batch processing (optional with parallel)
5) Auditable JSONL logs
#!/usr/bin/env bash
set -euo pipefail
# Config
AI_API_BASE="${AI_API_BASE:-https://api.openai.com}"
AI_MODEL="${AI_MODEL:-gpt-4o-mini}"
AI_TIMEOUT="${AI_TIMEOUT:-30}" # seconds per request
AI_MAX_ATTEMPTS="${AI_MAX_ATTEMPTS:-5}" # retries
AI_CACHE_DIR="${AI_CACHE_DIR:-.ai-cache}"
AI_LOG_JSONL="${AI_LOG_JSONL:-ai_calls.jsonl}" # append-only audit log
CIRCUIT_FILE="${CIRCUIT_FILE:-.ai-circuit}"
CIRCUIT_OPEN_SECS="${CIRCUIT_OPEN_SECS:-60}" # pause when tripped
CIRCUIT_FAIL_THRESHOLD="${CIRCUIT_FAIL_THRESHOLD:-5}"
mkdir -p "$AI_CACHE_DIR"
_now() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
_ts() { date -u +%s; }
# --- 1) Prompt templates with versioning ---
# Keep a version string to invalidate caches when your prompt changes materially.
PROMPT_VERSION="v1-severity-labeler"
# Render a prompt template. You can add variables and versions here.
render_prompt() {
local user_input="$1"
cat <<EOF
You are a precise log line labeler. Output just one label: ERROR, WARN, or INFO.
Be conservative: only choose ERROR if it's clearly an error.
---INPUT---
$user_input
---END---
EOF
}
# --- 2) Deterministic file-based cache ---
_hash() {
# Hash model + version + system + prompt for a stable key
local key="$1"
printf '%s' "$key" | sha256sum | awk '{print $1}'
}
cache_get() {
local key="$1" f="$AI_CACHE_DIR/$key.txt"
[[ -f "$f" ]] && cat "$f" && return 0
return 1
}
cache_put() {
local key="$1" value="$2" f="$AI_CACHE_DIR/$key.txt"
printf '%s' "$value" > "$f"
}
# --- 3) Robust request with timeout, retries, and circuit breaker ---
_circuit_is_open() {
[[ -f "$CIRCUIT_FILE" ]] || return 1
local last_fail ts_now=$(_ts)
last_fail=$(<"$CIRCUIT_FILE")
(( ts_now - last_fail < CIRCUIT_OPEN_SECS )) && return 0 || return 1
}
_trip_circuit() {
printf '%s' "$(_ts)" > "$CIRCUIT_FILE"
}
_clear_circuit() {
[[ -f "$CIRCUIT_FILE" ]] && rm -f "$CIRCUIT_FILE"
}
ai_call_raw() {
# Arguments: $1 = system_text, $2 = user_text
local system_text="$1" user_text="$2"
local payload tmp out status attempt=1 backoff=1
if _circuit_is_open; then
echo "Circuit open; backing off. Try again later." >&2
return 75 # EX_TEMPFAIL
fi
tmp="$(mktemp)"
payload="$(jq -n --arg model "$AI_MODEL" --arg sys "$system_text" --arg usr "$user_text" '{
model: $model,
messages: [
{role:"system", content:$sys},
{role:"user", content:$usr}
],
temperature: 0.2
}')"
while (( attempt <= AI_MAX_ATTEMPTS )); do
# Send request; capture HTTP code separately
status=$(
curl -sS -m "$AI_TIMEOUT" -w '%{http_code}' -o "$tmp" \
-H "Authorization: Bearer ${OPENAI_API_KEY:-}" \
-H "Content-Type: application/json" \
-X POST "$AI_API_BASE/v1/chat/completions" \
-d "$payload"
) || status="000"
if [[ "$status" == "200" ]]; then
_clear_circuit
out="$(jq -r '.choices[0].message.content // empty' < "$tmp")"
rm -f "$tmp"
printf '%s' "$out"
return 0
fi
# Retry on transient issues: 429, 500-599, or network (000)
if [[ "$status" == "429" || "$status" =~ ^5 || "$status" == "000" ]]; then
sleep "$backoff"
backoff=$(( backoff * 2 ))
attempt=$(( attempt + 1 ))
continue
fi
# Non-retryable: log and fail
cat "$tmp" >&2
rm -f "$tmp"
_trip_circuit
return 1
done
# Max attempts exceeded
cat "$tmp" >&2 || true
rm -f "$tmp"
_trip_circuit
return 1
}
# --- 4) High-level ask with caching and logging ---
ai_ask() {
# $1 = user text, $2? = optional system override
local user_text="$1"
local system_text="${2:-"You are a helpful assistant for shell and logs."}"
local prompt_full cache_key result
prompt_full="$(render_prompt "$user_text")"
cache_key="$(_hash "$AI_MODEL|$PROMPT_VERSION|$system_text|$prompt_full")"
if result="$(cache_get "$cache_key")"; then
printf '%s' "$result"
return 0
fi
result="$(ai_call_raw "$system_text" "$prompt_full")" || return $?
# Save to cache
cache_put "$cache_key" "$result"
# --- 5) Append audit log as JSONL ---
jq -n --arg t "$(_now)" \
--arg model "$AI_MODEL" \
--arg ver "$PROMPT_VERSION" \
--arg sys "$system_text" \
--arg usr "$prompt_full" \
--arg res "$result" \
'{ts:$t, model:$model, prompt_version:$ver, system:$sys, user:$usr, response:$res}' \
>> "$AI_LOG_JSONL"
printf '%s' "$result"
}
# CLI helper: read from args or STDIN
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
if [[ $# -gt 0 ]]; then
ai_ask "$*"
else
# Read entire STDIN as one prompt
ai_ask "$(cat)"
fi
fi
Save as ai_patterns.sh and make it executable:
chmod +x ai_patterns.sh
How to use it (real-world examples)
1) Single-shot classification (log severity)
echo "Disk quota exceeded for user alice" | ./ai_patterns.sh
# -> e.g., "ERROR"
2) Quick Q&A (reuse the same helper)
./ai_patterns.sh "In Bash, what's the difference between \$* and \$@?"
3) Batch labeling with parallel (rate-friendly)
Ensure
parallelis installed (see install commands above).Example: label the first 100 lines of a log file with 2 concurrent workers.
head -n 100 /var/log/syslog \
| parallel -j2 --line-buffer './ai_patterns.sh "{}"'
Notes:
Caching prevents re-billing for repeated inputs.
The built-in retries and circuit breaker help when APIs throttle you.
4) Audit and analyze outcomes with jq
- Inspect the last 3 calls:
tail -n 3 ai_calls.jsonl | jq .
- Count labels produced
jq -r '.response' ai_calls.jsonl | sort | uniq -c
Pattern recap and why they matter
Prompt templates + versioning
- Value: Makes experiments reproducible and cacheable. Bump
PROMPT_VERSIONwhen changing instructions to avoid stale cache hits.
- Value: Makes experiments reproducible and cacheable. Bump
Deterministic caching
- Value: Cuts cost and latency dramatically for repeated inputs. Hash model+prompt+version for stable keys.
Retries + backoff + circuit breaker
- Value: Survive transient 429/5xx errors without spamming the API. Circuit breaker prevents “thundering herd” after sustained failures.
Safe batch processing
- Value: Parallelization speeds up offline jobs while avoiding rate limit spikes. Combine
-jwith your cache and backoff.
- Value: Parallelization speeds up offline jobs while avoiding rate limit spikes. Combine
Auditable JSONL logs
- Value: Keep a trail of prompts and outputs to debug regressions, measure quality, and comply with governance requirements.
Installation summary (all cited tools)
Debian/Ubuntu:
sudo apt update sudo apt install -y curl jq parallelFedora/RHEL/CentOS:
sudo dnf install -y curl jq parallelopenSUSE:
sudo zypper refresh sudo zypper install -y curl jq parallel
Conclusion and next steps
You now have a small, production-grade toolkit for AI in Bash:
Templates you can iterate on without breaking reproducibility.
A cache that keeps costs down.
Resilient networking that respects timeouts and rate limits.
Batch workflows that scale.
A paper trail you can analyze later.
Your move:
Drop
ai_patterns.shinto your repo, set yourOPENAI_API_KEY, and try the examples.Extend the template for your use case (summarization, extraction to JSON, test-case generation).
Wire it into cron, CI, or data pipelines—and keep shipping.
If you want a follow-up, ask for a streaming variant, function calling pattern, or a SQLite cache alternative.