Shipping a Production Support Agent: Brain + Hands with Django, Redis, and WordPress

This post walks through a production-ready support agent with a Brain + Hands separation, wired into WordPress on the front, and Django on the back. The goal: predictable behavior, fast responses, measurable quality, and easy handoff to humans.

Use case
– Tier-1 support for order status, returns, product info, and FAQ
– Handoff to human when confidence is low or user requests it
– Works in a WordPress site widget, Slack, and email (shared backend)

Architecture (high level)
– Front-end: WordPress chat widget (vanilla JS) -> Django REST endpoint
– Brain: LLM for reasoning + routing (no direct data access)
– Hands: Tools in Django (Postgres + Redis) exposed via function-calling schemas
– Memory: Short-term thread memory (Redis), long-term knowledge (Postgres + pgvector)
– Orchestrator: Deterministic state machine (Django service + Celery tasks)
– RAG: Product/FAQ index with embeddings; constrained retrieval
– Observability: Request logs, traces, tool latency, outcomes, cost
– Deployment: Docker, Nginx, Gunicorn, Celery, Redis, Postgres

Brain + Hands separation
– Brain (LLM): Planning, deciding which tool to call, assembling final answer. No raw DB/API keys. Receives tool specs only.
– Hands (Tools): Deterministic, side-effect aware, with strict input/output schemas. Tools never “think”—they do.

Core tools (Hands)
– search_kb(query, top_k): RAG over Postgres+pgvector. Returns citations with IDs and source.
– get_order(email|order_id): Reads order status from internal service.
– create_ticket(email, subject, body, priority): Creates support case in helpdesk.
– handoff_human(reason, transcript_excerpt): Flags for live agent queue with context.

Tool contracts (JSON schema examples)
– search_kb input: { query: string, top_k: integer PLAN -> (TOOL_LOOP)* -> DRAFT -> GUARDRAIL -> RESPOND
– TOOL_LOOP limits to 3 tool calls per turn
– If Brain calls an unknown tool or wrong schema: correct and retry once, else fallback to handoff_human
– Timeouts: 3s per tool; overall SLA 6s; degrade mode returns partial + “We’re checking further via email” and opens ticket

Guardrails
– Content filter: block sensitive/abusive content; offer handoff
– PII sanitizer: mask tokens before vector search
– Citation checker: if answer references kb, verify at least one valid citation is present
– Safety fallback: neutral response + create_ticket when filter trips

RAG implementation
– Storage: Postgres with pgvector for embeddings
– Chunking: 512–800 tokens, overlap 80
– Metadata: doc_id, section, source, updated_at, allowed_channels
– Query: Hybrid BM25 + vector; re-rank top 8 to 3
– Response: Return only snippets + URLs; Brain composes final with citations “(See: Title)”

Error handling
– Tool failures: exponential backoff (200ms, 400ms); then circuit-break for 60s
– LLM failures: switch to fallback model on timeout; respond with concise generic + ticket
– Data drift: if RAG index empty or stale, disable search_kb and escalate

WordPress integration
– Front-end widget: Minimal JS injects a floating chat; posts to /api/agent/messages with thread_id and csrf token nonce
– Auth: Public sessions get rate-limited by IP + device fingerprint; logged-in users attach JWT from WordPress to Django via shared secret
– Webhooks: Ticket created -> WordPress admin notice and email; agent takeover -> support Slack channel

Django endpoints (concise)
– POST /api/agent/messages: { thread_id, user_msg }
– GET /api/agent/thread/{id}: returns last N messages + status
– POST /api/agent/feedback: thumbs_up/down, tags
– Admin: /admin/agent/tools, /admin/agent/kb, /admin/agent/metrics

Celery tasks
– run_brain_step(thread_id)
– execute_tool(call_id)
– rebuild_kb_index()
– nightly_eval() against golden test set

Model selection
– Primary: a function-calling LLM with low latency (e.g., GPT-4o-mini or Claude Sonnet-lite). Keep token limits reasonable.
– Fallback: cheaper model with same tool schema to maintain compatibility.
– Temperature: 0.2 for tool routing, 0.5 for final drafting.

Cost and latency targets
– P50: 1.4s response (no tools), 2.8s with RAG, 3.5s with order lookup
– P95: <5s
– Cost: X%

Deployment notes
– Docker services: web (Gunicorn), worker (Celery), scheduler (Celery Beat), redis, postgres, nginx
– Readiness probes: tool ping, RAG index freshness, model API status
– Secrets: mounted via Docker secrets; rotate quarterly
– Blue/green deploy: drain workers, warm RAG cache, switch traffic

Minimal data models
– threads(id, user_id, channel, status, created_at)
– messages(id, thread_id, role, content, tool_name?, tool_payload?, created_at)
– kb_docs(id, title, url, text, embedding, updated_at, allowed_channels)
– tickets(id, thread_id, external_id, status, priority, created_at)

Snippet: tool call flow (pseudo)
– User -> /messages
– Orchestrator builds context from Redis + last N messages
– Brain returns tool_call: search_kb
– Celery executes search_kb, stores items
– Brain drafts answer with citations
– Guardrail checks
– Respond; optionally create_ticket if unresolved

Rollout plan
– Phase 1: FAQ-only RAG; no order lookups; human-in-the-loop
– Phase 2: Enable get_order with safe whitelist; add evals
– Phase 3: Enable create_ticket + SLA timers
– Phase 4: Add Slack channel and email ingestion to same backend

What to avoid
– Letting the Brain call HTTP endpoints directly
– Unbounded memory growth in Redis
– RAG over unreviewed or user-generated content
– Returning tool stack traces to users

Repository checklist
– /orchestrator: state machine, guardrails
– /tools: deterministic functions, schemas, tests
– /brain: prompt templates, model client, retries
– /kb: loaders, chunker, embeddings, indexer
– /web: Django views, serializers, auth
– /ops: docker-compose, nginx, CI, eval harness, dashboards

This pattern gives you a predictable, support-ready agent that integrates cleanly with WordPress, scales under load, and stays auditable.

Building a Production-Ready Support Agent for WordPress (Brain + Hands Architecture)

This post describes a production-ready customer support agent that runs inside WordPress while delegating orchestration and tools to a secure backend. It’s engineered for reliability, observability, and low latency.

Use case
– Answer product and account questions for a WooCommerce site
– Escalate to human when confidence is low
– Take actions: lookup orders, issue refunds (guardrails), update tickets, send emails

High-level architecture
– Frontend (WordPress plugin)
– Chat UI + streaming
– Session auth via signed JWT from WP
– Minimal logic; forwards events to backend
– Brain (Django service)
– Orchestrator with policy, planning, and tool routing
– Provider-agnostic LLM client with timeouts, retries, circuit breakers
– Memory: short-term conversation state + RAG for docs + customer context
– Hands (tools)
– WooCommerce API (read + guarded write)
– Vector search for KB (Postgres pgvector or Pinecone)
– Email/Slack/webhook notifications
– Ticketing (GitHub Issues, Linear, or HelpScout)
– Infra
– Postgres (sessions, logs, transcripts, evals)
– Redis (queues, rate limits, short-term state)
– Celery/RQ workers for long I/O tasks
– S3/GCS for attachments
– Nginx + Gunicorn/Uvicorn
– Feature flags via environment or LaunchDarkly

Brain + Hands separation
– Brain responsibilities
– Interpret user intent, decide tool plans, enforce policies
– Choose between “answer from RAG” vs “call WooCommerce” vs “escalate”
– Keep tight token budgets and latency budgets
– Hands responsibilities
– Deterministic, audited side effects
– Strict schemas, input validation, and permission checks
– Idempotent operations with clear error codes

Data model (Postgres)
– users: wp_user_id, email, roles
– sessions: session_id, user_id, created_at, last_seen
– messages: session_id, role, content, tool_calls, latencies
– kb_docs: id, url, title, chunk_id, embedding
– eval_runs: scenario_id, score, metrics, model, timestamp
– actions_log: tool_name, request, response, status, cost

Tooling interfaces (Hands)
– Tool schema example
– name: get_order_status
– input: {order_id: string}
– output: {status, items[], total, updated_at}
– errors: NOT_FOUND, UNAUTHORIZED, RATE_LIMIT, UPSTREAM_5XX
– Guarded write tool
– name: issue_refund
– preconditions: user identity verified, order within policy window, amount soft_limit
– RAG search tool
– name: kb_search
– input: {query: string, top_k: int<=5}
– output: [{title, url, snippet, score}]
– Messaging tools
– name: notify_human
– name: create_ticket

Prompting and policies (Brain)
– System prompt
– You are a support agent for ACME Store. Answer strictly from tools or KB. If unsure, escalate.
– Never fabricate order details. Use get_order_status.
– For refunds, always run verify_identity then check_refund_policy.
– Summarize actions taken at the end, link to sources.
– Planning logic
– If user mentions an order identifier → get_order_status
– If how-to/product question → kb_search then compose
– If policy question not in KB → escalate
– Style rules
– Short answers, steps, and links to sources
– Don’t expose internal error traces

Memory strategy
– Short-term: last 10 messages in Redis keyed by session_id
– Long-term: store all messages in Postgres for analytics
– Entity memory: customer profile (name, last orders, subscription) fetched on session start, cached 15 minutes
– RAG: nightly KB crawl (docs, FAQs, policies), chunked and embedded; invalidate on content updates

Orchestration flow
1) WP plugin sends user message + session_id to Django
2) Brain assembles context: last 10 messages, customer profile, latency budget
3) Brain calls plan() to choose tools
4) Execute tool calls with timeouts and retries (exponential backoff, jitter)
5) If tools fail with 5xx, trigger fallback: reduced context + alternative provider
6) Compose final answer with citations
7) Stream tokens back to WP; log telemetry

Error handling and guardrails
– Timeouts: 2.5s LLM planning; 3s tool I/O (retry up to 2x)
– Circuit breakers: open per-tool after 5 failures/60s, route to fallback answer + escalate
– Input validation: pydantic/dataclasses schemas for all tool inputs
– PII scrubbing before logs; hashed identifiers
– Rate limits: per-user and global; shed load with friendly message
– High-risk actions (refunds) require dual confirmation path or human approval token

Latency and cost controls
– Small model for planning, larger model only when composing long answers
– Token budget caps by policy; truncate history with windowing
– Cache KB search for 60s per session
– Turn off streaming if bandwidth-constrained; send final only

Observability
– Structured logs: request_id, session_id, tool_name, duration_ms, token_usage, cost_usd
– Traces: OpenTelemetry spans around LLM calls and tools
– Dashboards: success rate, handoffs, mean latency, top errors, containment rate
– Red-teaming playbook: simulate tricky prompts weekly; auto-generate counterfactual tests

Evaluation harness
– Scenarios: “Where is my order?”, “Refund outside window”, “Change shipping address”
– Metrics: exactness, policy adherence, source coverage, latency
– Offline evals on synthetic+real transcripts
– Canary release: 5% traffic to new Brain version, compare containment and CSAT

WordPress integration (plugin)
– Shortcode to render chat widget
– Auth: WP nonce → backend JWT (scoped to session only)
– Endpoints
– POST /chat/send
– GET /chat/stream?session_id=…
– POST /webhooks/order_updated
– Admin settings
– Backend URL, API key, rate limit per minute
– Feature flags: enable_refunds, enable_streaming
– Privacy: transcript retention days, PII masking rules

Security notes
– Store only necessary fields; encrypt at rest
– Strict CORS and allowed origins
– Signed webhooks for WooCommerce events
– Secrets via env vars (no secrets in WP DB)

Deployment checklist
– Docker images for Django API and workers
– Nginx reverse proxy with request size and timeout limits
– Postgres + Redis managed services
– Migrate DB, seed KB embeddings
– Health checks, readiness probes
– Runbooks for incident response and on-call rotation

Model/providers
– Start: gpt-4o-mini or claude-3-haiku for planning; gpt-4o or claude-3.5-sonnet for final answers
– Fall back chain: Provider A → Provider B → cached KB-only answer → human handoff
– Track provider performance per scenario; auto-shift traffic via feature flags

Rollout plan
– Week 1: shadow mode (agent suggests, human replies)
– Week 2: low-risk actions enabled, no refunds
– Week 3: enable refunds under $50 with approval token
– Ongoing: weekly evals, error budget, model updates behind canaries

What to build first
– KB pipeline (crawler, chunker, embeddings)
– Minimal Brain with plan() + kb_search + compose
– Observability + eval harness
– Then add WooCommerce read tools, finally guarded write tools

This blueprint keeps WordPress simple and pushes orchestration, policy, and tools into a hardened backend. You get fast responses, safe actions, measurable performance, and a path to scale.

Shipping a Production-Ready Support Agent: Brain + Hands Architecture with Django, Celery, and Redis

This is a production pattern I use for customer support agents that answer FAQs, triage tickets, and trigger workflows (refunds, RMA, status checks) across web chat, email, and Slack. It separates Brain (reasoning) from Hands (tools), runs on Django + Celery + Redis, and is observable, testable, and safe.

Core goals
– Deterministic routing and tool usage
– Strong guardrails, timeouts, and fallbacks
– Telemetry and replay for every decision
– Multi-tenant, multi-channel, cost-aware

High-level architecture
– Ingress: Web chat (WebSocket/REST), Email (inbound webhook), Slack (Events API).
– Router: Normalizes messages to a canonical envelope and selects an agent profile.
– Brain: LLM planner (reasoning) that decides intent and tool calls via constrained JSON.
– Hands: Tool layer (pure functions) with validation, auth, and rate limits.
– Memory: Short-term (conversation window), long-term (vector store), and case state (Postgres).
– Orchestrator: Celery tasks for async tool calls, retries, and circuit breakers.
– Store: Postgres (state/logs), Redis (queues/locks), S3 (artifacts/transcripts).
– Observability: OpenTelemetry traces, structured logs, per-step timing and cost.

Data contracts
– MessageEnvelope: {tenant_id, channel, user_id, session_id, text, locale, attachments[], metadata{}}
– BrainPlan (strict tool schema): {intent, steps:[{tool, args, on_error}], final_answer, citations[]}
– ToolResult: {tool, ok, data|error, cost_ms}
– TraceEvent: {correlation_id, span, data, ts}

Brain + Hands separation
– Brain never mutates external systems. It plans.
– Hands are the only side-effect layer, gated by policy and schemas.
– Planner outputs are JSON that must parse and validate or are rejected.

Minimal Django models
– AgentSession(id, tenant_id, channel, user_id, status, last_seen_at)
– Message(id, session_id, role, content, tokens, cost_usd, latency_ms)
– ToolCall(id, session_id, tool, args_json, status, error, latency_ms)
– CaseState(id, session_id, intent, priority, properties_json)
– AuditLog(id, session_id, event_type, payload_json)

Routing (deterministic)
– Exact-match tenant policy first (e.g., SLA, hours, forbidden tools)
– Channel constraints second (e.g., Slack safe mode)
– Intent classifier last (small local model or regex first-pass)

Tool design
– Every tool is a small, idempotent function with:
– Pydantic schema for args and output
– Timeout, retry policy, circuit breaker
– Auth scope mapping per tenant
– Rate limit token bucket (Redis)
– Redaction rules for logs
– Example tools: get_order_status, create_ticket, process_refund, faq_search, escalate_to_human

Example: tool schema (Pydantic)
class GetOrderStatusArgs(BaseModel):
order_id: constr(strip_whitespace=True, min_length=6, max_length=32)

class GetOrderStatusOut(BaseModel):
status: Literal[“processing”,”shipped”,”delivered”,”cancelled”]
eta: Optional[str]
last_update: str

Brain contract (constrained JSON)
system_prompt (short):
– You are the Planner. Decide intent and tools using the given schemas.
– Use at most 2 tools before responding.
– If confidence plan_message -> execute_tools -> render_answer -> dispatch_reply
– Each step emits a trace span with timing, token usage, and cache hits.

Sample Django/Celery skeleton
# tasks.py
@app.task(bind=True, soft_time_limit=5)
def plan_message(self, envelope_id):
env = load_envelope(envelope_id)
context = build_context(env) # last N messages, case state, tenant policy
plan_json = call_planner(context) # with JSON mode + schema
plan = validate_plan(plan_json)
save_plan(plan)
execute_tools.delay(env.session_id, plan)

@app.task(bind=True, soft_time_limit=10, max_retries=1)
def execute_tools(self, session_id, plan):
results = []
for step in plan[“steps”]:
res = run_tool_safe(session_id, step) # timeout, retry, circuit breaker
results.append(res)
if not res[“ok”] and step.get(“on_error”) == “escalate_to_human”:
queue_handoff(session_id)
return
render_answer.delay(session_id, plan, results)

@app.task
def render_answer(session_id, plan, results):
answer = build_answer(plan, results) # template + grounded facts
persist_and_dispatch(session_id, answer)

Tool runner (guardrails)
def run_tool_safe(session_id, step):
tool = TOOL_REGISTRY[step[“tool”]]
args = tool.args_model(**step[“args”]) # validation
with circuit(tool.name).call(timeout=2):
data = tool.fn(args)
out = tool.out_model(**data)
return {“tool”: tool.name, “ok”: True, “data”: out.dict(), “cost_ms”: …}

Memory strategy
– Short-term: windowed retrieval of last K messages with role-aware pruning.
– Long-term: vector store of FAQs and policies (bm25 + embeddings). Tools: faq_search(query) returns top-3 chunks with source URLs.
– Case state: lightweight JSON with intent, artifacts, and SLA flags.

Hallucination control
– Tools return authoritative facts. Answers must cite tool outputs or approved docs.
– If no citation or tool data, say “I’m not certain” and escalate or ask for clarification.
– Instruction: never fabricate order IDs, dates, or amounts.

Cost and latency
– Use small planning model for intent; large model only when required.
– Cache embeddings and tool results (e.g., memoize get_order_status 60s).
– Token budgeting: truncate history by tokens, not message count.
– Parallel tool calls when independent (fan-out in Celery group).

Error handling and fallbacks
– Timeouts: 2s per tool; 6s end-to-end target.
– Retries: 1 retry for transient 5xx; no retry for 4xx.
– Circuit breaker: open after 3 failures/60s per tool+tenant.
– Safe response on failure with human handoff ticket ID.
– Dead-letter queue for poisoned messages, with replay UI.

Security and privacy
– Per-tenant API keys and scopes for each tool.
– PII redaction in logs; encryption at rest for transcripts.
– Prompt firewall: block secrets, card numbers, and auth tokens.
– Model routing by data classification; no PII to third-party LLMs if policy forbids.

Testing and evaluation
– Golden paths: 20-50 real transcripts turned into fixtures.
– Adversarial tests: tool failure, slow APIs, off-topic inputs.
– Offline agent eval: replay plans, measure tool accuracy, citation coverage.
– Shadow mode: run agent silently for a week before enabling auto-resolve.

Deployment notes
– Django + Gunicorn for API; Celery workers with autoscaling; Redis for queues/locks.
– Blue/green deploy; feature flags per tenant and channel.
– Observability: OpenTelemetry to your APM; log per step with correlation_id.
– Cost dashboards: tokens by tenant, intent, channel.

Example planner call (Python)
def call_planner(context):
resp = llm.chat(
model=”gpt-4o-mini”,
messages=[{“role”:”system”,”content”:SYSTEM_PROMPT},{“role”:”user”,”content”:context}],
response_format=BrainPlanSchema # JSON mode
)
return resp.parsed

What to ship this week
– Implement the data models, Tool registry, and 3 tools (faq_search, get_order_status, create_ticket).
– Wire Celery pipeline and Redis rate limits.
– Add JSON schema validation and OpenTelemetry spans.
– Run in shadow mode on Slack for one tenant.

Shipping a Production-Ready Brain+Hands Support Agent (WordPress + Python API)

This build delivers a support/billing agent you can actually ship. It follows Brain+Hands separation, explicit tool contracts, a deterministic state machine, and secure backend tools. Stack: WordPress (frontend), Python FastAPI (tools + orchestrator), Redis (state/cache), Postgres (KB + logs), vector DB (memory), OpenAI/Groq/Anthropic (LLM).

1) Architecture overview
– Brain (LLM policy): Plans, chooses tools, reasons. No direct DB/API access.
– Hands (tools): Idempotent HTTP endpoints with strict schemas. Observable, rate-limited, auditable.
– Orchestrator: State machine controlling turns, tool calls, retries, and timeouts.
– Memory:
– Short-term: per-session scratchpad (Redis).
– Knowledge: vector search over product docs/FAQ.
– Facts: authoritative store lookups (billing, orders).
– Guardrails: auth, PII redaction, tool allowlist, cost/time budgets, circuit breakers.
– Integration: WordPress plugin sends chat events to orchestrator; streaming tokens back to UI.

2) Contracts first (make tools boring and safe)
Define JSON schemas for every tool. Keep them narrow, idempotent, and testable.

Example tool manifest (slice):
{
“name”: “get_order_status”,
“description”: “Return current order state for a given order_id.”,
“method”: “POST”,
“url”: “https://api.example.com/tools/get_order_status”,
“input_schema”: {
“type”: “object”,
“properties”: {
“order_id”: {“type”: “string”, “pattern”: “^[A-Z0-9-]{6,}$”}
},
“required”: [“order_id”],
“additionalProperties”: false
},
“output_schema”: {
“type”: “object”,
“properties”: {
“status”: {“type”: “string”},
“updated_at”: {“type”: “string”, “format”: “date-time”}
},
“required”: [“status”]
},
“timeouts_ms”: 2500,
“retries”: 1
}

3) Hands: secure Python FastAPI tools
– Enforce schema at the edge.
– Require JWT with narrow scopes.
– Add rate limits and audit logs.

from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel, Field
import time

app = FastAPI(title=”SupportAgentTools”)

class OrderReq(BaseModel):
order_id: str = Field(min_length=6, pattern=r”^[A-Z0-9-]{6,}$”)

class OrderResp(BaseModel):
status: str
updated_at: str | None = None

def auth(scope: str):
def _auth(token=Depends(…)): # your JWT dependency
if scope not in token.scopes:
raise HTTPException(403, “forbidden”)
return token.sub
return _auth

@app.post(“/tools/get_order_status”, response_model=OrderResp)
def get_order_status(req: OrderReq, _=Depends(auth(“order:read”))):
start = time.time()
# query read replica; maintain SLO use respective tools.
– General product usage -> retrieve_docs.
– Anything else -> answer concisely or ask a clarifying question.

6) Orchestrator: a small, reliable state machine
States:
– RECEIVE -> PLAN -> EXECUTE_TOOL? -> OBSERVE -> RESPOND -> END

Pseudo:
def handle_turn(msg, session_id):
budget = Budget(tokens=3000, tools=3, wall_ms=8000)
state = “PLAN”
memory = load_session(session_id)
while budget.ok() and state != “END”:
if state == “PLAN”:
action = llm_policy(memory, tool_manifest)
if action.type == “tool”:
state = “EXECUTE_TOOL”
else:
state = “RESPOND”
elif state == “EXECUTE_TOOL”:
result = call_tool(action.name, action.args, timeout=manifest[action.name].timeouts_ms)
record_observation(result)
state = “OBSERVE”
elif state == “OBSERVE”:
memory.update_with_observation(result)
if need_more_tools(result): state = “PLAN”
else: state = “RESPOND”
elif state == “RESPOND”:
reply = llm_response(memory)
emit_stream(reply)
state = “END”

Controls:
– Max 2 tool calls/turn for latency.
– Tool circuit breaker after 2x p95 failures.
– Token + time budgets enforced per turn.

7) Retrieval that doesn’t hallucinate
– Chunk docs to 300–500 tokens with overlap 50–100.
– Store title, URL, product tags, and last_updated.
– Rerank top 20 -> 5 with a fast cross-encoder or LLM-judge at small context.
– In answers, include “According to ()” and quote minimal lines.<br /> – Evict stale docs with last_updated TTL checks.</p> <p>8) Error handling and fallbacks<br /> – Tool error classes: 4xx user-fixable (show guidance), 5xx transient (retry with jitter), timeout (offer manual escalation).<br /> – If tools unavailable, switch to knowledge-only mode and surface a status note to the user.<br /> – Log: request_id, user_hash, tool_calls, latencies, token_usage, model, success_flag.</p> <p>9) WordPress integration (plugin sketch)<br /> – Shortcode [aiguy_chat] renders chat UI.<br /> – Frontend calls /wp-json/aiguy/v1/chat (nonce protected).<br /> – Server proxy signs JWT to orchestrator and streams chunks back.</p> <p>PHP (very abbreviated):<br /> add_action(‘rest_api_init’, function () {<br /> register_rest_route(‘aiguy/v1’, ‘/chat’, [‘methods’=>’POST’,’callback’=>’aiguy_chat’,’permission_callback’=>’__return_true’]);<br /> });<br /> function aiguy_chat(WP_REST_Request $r) {<br /> $jwt = make_scoped_jwt([‘aud’=>’orchestrator’,’scopes’=>[‘chat:send’]]);<br /> $resp = wp_remote_post(‘https://agent.example.com/chat’, [<br /> ‘headers’=>[‘Authorization’=>”Bearer $jwt”],<br /> ‘body’=>[‘session_id’=>get_session_id(), ‘message’=>$r->get_param(‘message’)],<br /> ‘timeout’=>15<br /> ]);<br /> return rest_ensure_response(wp_remote_retrieve_body($resp));<br /> }</p> <p>10) Models and performance<br /> – Use fast model for planning (e.g., gpt-4o-mini, llama-3.1-70b-instruct via Groq) and a stronger model for final generation when needed.<br /> – Target p95 < 2.5s single-turn with 0–1 tool; < 4.5s with 2 tools.<br /> – Cache retrieval and deterministic tool schemas to reduce tokens.</p> <p>11) Security checklist<br /> – Tool allowlist + strict JSON schemas.<br /> – JWT with narrow scopes + rotation.<br /> – PII redaction before logs; encrypt sensitive fields at rest.<br /> – Separate read/write tools; require user confirmation for writes.<br /> – Rate limit per IP/user/session; WAF on tool API.</p> <p>12) Observability<br /> – OpenTelemetry spans: plan, tool call, observe, generate.<br /> – Emit metrics: latency p50/p95, tool error rate, deflection rate, CSAT.<br /> – Log all prompts/responses with redaction; enable replay in a sandbox.</p> <p>13) Deployment<br /> – Dockerize orchestrator + tools. Separate autoscaling for tools with spiky I/O.<br /> – Blue/green deploy; health checks include LLM warmup and tool canary.<br /> – Backpressure: queue requests when model or DB under load; shed load gracefully with a user-facing “email me results” fallback.</p> <p>14) Minimal smoke test<br /> – Happy path: “Where is my order ABC123?”<br /> – Tool timeout path: inject 2s delay; verify graceful message.<br /> – Hallucination guard: ask for unavailable feature; ensure denial with doc citation.</p> <p>Deliverables you can ship this week<br /> – Tool API (FastAPI) with 3 tools: get_order_status, get_invoice_pdf, retrieve_docs.<br /> – Orchestrator with state machine, budgets, and streaming.<br /> – WordPress plugin with nonce-protected REST route and basic chat UI.<br /> – Vector index with top 20 support articles and citations in answers.<br /> – Dashboards: latency, tool errors, deflection, cost.</p> </div> </article> <article id="post-240" class="post-240 post type-post status-publish format-standard has-post-thumbnail hentry category-ai-agents-chatbots"> <h1 style="margin-bottom: 20px; color: var(--primary);">Shipping a Production-Ready Support Agent (Brain + Hands) with WordPress Frontend and Django Backend</h1> <div class="entry-content"> <p>This post shows how to ship a real support agent that answers questions and takes actions (tickets, refunds, account checks) using a WordPress frontend and a Django/Celery backend. It follows a Brain + Hands architecture with strict contracts, scalable orchestration, and production safety.</p> <p>Use case<br /> – Customer support assistant for ecommerce SaaS<br /> – Must answer FAQs, retrieve orders, open tickets, issue refunds (with approval), and post internal notes to Slack<br /> – Runs in WordPress, backed by a Django API and Celery workers<br /> – Auditable, rate-limited, and fault-tolerant</p> <p>Architecture (Brain + Hands)<br /> – Brain (policy/planner):<br /> – LLM with tool calling and JSON schema guarantees<br /> – Tasks: classify intent, plan minimal actions, call tools, summarize results, ask for approval when needed<br /> – Prompt enforces allowed tools, budgets, and safety<br /> – Hands (tools/microservices):<br /> – shopify_get_order, zendesk_create_ticket, stripe_issue_refund, slack_post_message, knowledge_search<br /> – Each is an HTTP endpoint or Python function with strict IO schema, timeouts, retries, and idempotency<br /> – Orchestrator:<br /> – Django REST API receives messages, persists state in Postgres<br /> – Celery workers execute tool calls with backoff and circuit breakers<br /> – Redis for short-lived state and rate limiting<br /> – pgvector for semantic memory and knowledge embeddings<br /> – Frontend (WordPress):<br /> – Minimal plugin posts chat turns to /api/agent/messages<br /> – Streams token output back via SSE or polling<br /> – Observability:<br /> – Structured events (message, tool_invocation, tool_result, error, approval_request)<br /> – OpenTelemetry traces tagged with case_id/session_id</p> <p>Core data models<br /> – Conversation: id, user_id, channel, created_at<br /> – Turn: id, conversation_id, role (user/ai/tool/system), content, tokens_in/out, cost_estimate, tool_name, tool_args, tool_result_ref, error<br /> – Memory:<br /> – episodic_memory (Redis, TTL 24h, last 20 turns)<br /> – semantic_memory (Postgres+pgvector): id, embedding, text, source, tags<br /> – business_facts (YAML/JSON in Postgres or S3): SLAs, refund_policy, playbooks</p> <p>Tool contract (example)<br /> – Tool: shopify_get_order<br /> – Input schema: { order_id: string }<br /> – Output schema: { order_id: string, status: string, items: [{sku, qty, price}], customer_email: string, created_at: string }<br /> – Timeouts: 3s connect, 10s total<br /> – Retries: 2 with jittered backoff<br /> – Idempotency-Key: conversation_id + turn_id + tool_name + hash(args)<br /> – Tool: stripe_issue_refund<br /> – Input: { charge_id: string, amount_cents: integer, reason: enum[requested_by_customer, duplicate, fraudulent] }<br /> – Output: { refund_id: string, status: enum[pending,succeeded,failed] }<br /> – Requires approval: true when amount_cents > 10000</p> <p>System prompt (abbreviated)<br /> – Role: You are the Support Agent Brain. You can call only the registered tools. Always return JSON conforming to the response schema. Use the minimal set of tools to resolve the user’s request. Ask for approval before irreversible actions or refunds > $100.<br /> – Policies: Do not invent data. If a tool fails twice, escalate with a clear message and log error. Respect budgets: max 2 tools per turn, max 800 output tokens.<br /> – Memory: Retrieve top 5 knowledge_search contexts when the question is informational. For account-specific questions, attempt shopify_get_order first if the user provides an order id or email.</p> <p>Response envelope (from Brain to Orchestrator)<br /> – type: either “final_message” or “tool_call”<br /> – tool_call: { name: string, arguments: JSON }<br /> – final_message: { text: string, citations?: [source_ids], approvals_needed?: [{action, reason, suggested_args}] }</p> <p>Orchestration flow<br /> 1) Intake: POST /api/agent/messages {conversation_id, user_text, session_meta}<br /> 2) Retrieve episodic context (Redis) + semantic memory (pgvector top-k)<br /> 3) Brain call with tools manifest and context<br /> 4) If tool_call:<br /> – Enqueue Celery task hands.execute with idempotency<br /> – On success/failure, write Turn with tool_result/tool_error<br /> – Re-enter Brain with tool_result to continue the plan<br /> 5) If approvals_needed:<br /> – Emit approval card to WordPress, pause plan until POST /api/agent/approvals<br /> 6) Final_message streams to WordPress<br /> 7) Log events, update analytics, enforce cost limits</p> <p>Safety and guardrails<br /> – Least privilege API keys per tool; no cross-tool credential reuse<br /> – PII redaction before logging; encrypt at rest for sensitive payloads<br /> – Circuit breaker per tool: open after 5 errors/60s; degrade gracefully to “ticket only”<br /> – Human-in-the-loop for refunds > $100 and any failed KYC/AVS signals<br /> – Output verification: validate Brain JSON against schema before executing</p> <p>WordPress integration (minimal)<br /> – Frontend: a shortcode [ai_support_chat] renders a chat box<br /> – JS posts to /api/agent/messages with wp_nonce and user_id<br /> – SSE endpoint /api/agent/stream?conversation_id=… streams tokens<br /> – Admin settings: backend_base_url, api_key, streaming_on, approvals_webhook_url<br /> – Webhook: when approval requested, WP shows an approval card with Approve/Deny that hits /api/agent/approvals</p> <p>Backend routes (Django REST)<br /> – POST /api/agent/conversations -> {conversation_id}<br /> – POST /api/agent/messages -> creates Turn; triggers orchestrator; returns stream_url<br /> – GET /api/agent/stream -> SSE tokens with event types: chunk, tool, approval, end<br /> – POST /api/agent/approvals -> {conversation_id, turn_id, approved: bool, notes}<br /> – POST /api/agent/tools/callback (optional for async tools)<br /> – POST /internal/ingest-knowledge -> upsert documents and embeddings</p> <p>Memory strategy<br /> – Short-term: last 8-12 relevant turns (Redis)<br /> – Long-term: semantic facts and resolved tickets (pgvector, cosine; k=5-8)<br /> – Business facts: YAML playbooks (refund thresholds, outage responses) loaded into prompt header; versioned and cachable<br /> – Eviction: TTL for episodic; archive older embeddings monthly</p> <p>Error handling patterns<br /> – Tool execution:<br /> – Try/except with structured errors {type, message, retriable}<br /> – Retry retriable up to 2x; otherwise escalate with user-safe message<br /> – LLM failures:<br /> – If schema invalid, repair with a “validator-repair” pass (cheap model) then continue<br /> – Fallback model if primary degraded<br /> – Timeouts:<br /> – Brain call: 20s; tools: 10s; overall turn SLA: 30s<br /> – Idempotency: dedupe by (conversation_id, user_turn_id)</p> <p>Cost and performance controls<br /> – Cache knowledge answers for 30 minutes keyed by question hash<br /> – Compress history to summaries after 10 turns<br /> – Token budgets: truncate context to budget; prefer citations over long paraphrases<br /> – Batch embeddings; upsert via COPY for large loads<br /> – Prefer JSON mode/tool calling models to reduce hallucinations and token waste</p> <p>Deployment notes<br /> – Containers: nginx (TLS) | django (gunicorn) | celery-worker | celery-beat | redis | postgres+pgvector<br /> – Env: ENV=prod, OPENAI_API_KEY, SHOPIFY_KEY, ZENDESK_KEY, STRIPE_KEY, SLACK_TOKEN, WORDPRESS_SHARED_SECRET<br /> – Migrations enable pgvector: CREATE EXTENSION IF NOT EXISTS vector;<br /> – Health checks and liveness probes for each service<br /> – Backups: Postgres daily; object storage for logs and embeddings<br /> – Observability: OpenTelemetry + OTLP exporter; dashboards for token usage, tool error rate, approval queue age</p> <p>Example tool manifest (Brain-visible)<br /> – name: “shopify_get_order”<br /> – description: “Fetch an order by id or customer email. Use before refunding.”<br /> – input_schema: { order_id?: string, customer_email?: string } (at least one required)<br /> – name: “zendesk_create_ticket”<br /> – input_schema: { subject: string, body: string, requester_email: string, priority: enum[low,normal,high] }<br /> – name: “stripe_issue_refund”<br /> – input_schema: { charge_id: string, amount_cents: integer, reason: enum[requested_by_customer,duplicate,fraudulent] }<br /> – approvals: “required if amount_cents > 10000”<br /> – name: “knowledge_search”<br /> – input_schema: { query: string, top_k: integer }</p> <p>Quick test plan<br /> – Happy path: “Refund order 1234 for $50.” -> fetch order -> approval not needed -> refund -> confirm<br /> – Approval path: “Refund $200.” -> approval card rendered -> approve -> refund -> confirm<br /> – Failure path: Shopify outage -> circuit open -> create Zendesk ticket with context -> notify user<br /> – Informational: “What’s the SLA?” -> knowledge_search -> cite policy doc</p> <p>What to log per turn<br /> – latency_ms, model, tokens_in/out, cost_estimate_usd<br /> – user_intent, selected_tools, retry_count<br /> – approval_required, approval_decision_time<br /> – tool_errors and circuit state</p> <p>When to ship<br /> – You have passing e2e tests for the four test paths above<br /> – Tool timeouts and retries verified<br /> – Approval flow exercised in staging WordPress<br /> – Dashboards show stable p95 latencies and <2% tool error rate over 48 hours</p> <p>This pattern is production-ready, debuggable, and safe. You can extend it with more tools (CRM, shipping) without changing the Brain contract—just register new Hands with schemas and policies.</p> </div> </article> <article id="post-226" class="post-226 post type-post status-publish format-standard has-post-thumbnail hentry category-ai-agents-chatbots"> <h1 style="margin-bottom: 20px; color: var(--primary);">Shipping a Production-Ready WordPress Support Agent (Brain + Hands, Secure Tools, Real Logs)</h1> <div class="entry-content"> <p>This post walks through a production-ready support agent for a WooCommerce WordPress site. It follows a Brain + Hands architecture, uses secure tool calls, and ships with observability and guardrails. The goal: cut response time and ticket volume without risking data leaks or hallucinated actions.</p> <p>Use case<br /> – Answer product FAQs from docs<br /> – Check order status<br /> – Create/assign support tickets<br /> – Escalate when confidence is low<br /> – Log everything for audit and iteration</p> <p>High-level architecture (Brain + Hands)<br /> – Brain (LLM policy + reasoning):<br /> – Interprets user intent<br /> – Plans which tools to call and in what order<br /> – Produces final response or escalation note<br /> – Hands (tools + services):<br /> – read_kb: product/FAQ retrieval (RAG)<br /> – get_order_status: query WooCommerce/DB<br /> – create_ticket: issue system<br /> – send_email/update_user_note: notification<br /> – Orchestrator:<br /> – Validates tool requests (schema, authz)<br /> – Executes tools with timeouts/retries<br /> – Maintains short-term memory and trace<br /> – Enforces rate limits and cost caps</p> <p>Data flows<br /> – Public content: vector store from docs, product pages, and how-tos<br /> – Private content: order and ticket data via API with scoped tokens<br /> – No raw PII in prompts; use stable IDs and redact before logging</p> <p>Core components<br /> – LLM: gpt-4.1-mini or equivalent, with tool/function calling<br /> – Vector store: pgvector or Pinecone<br /> – App server: Python (FastAPI) or Node (Express) behind API gateway<br /> – Queue: Redis or SQS for deferred tasks (emails, ticket creation)<br /> – WordPress bridge: minimal plugin that proxies chat to backend with JWT</p> <p>Prompt design (Brain)<br /> – System role:<br /> – You are the Support Agent for [Brand]. Be concise, cite sources when from the KB, never guess order data, never disclose internal IDs. If confidence [{chunk, source, score}]<br /> – get_order_status(order_id: string, user_token: string) -> {status, eta, items[]}<br /> – create_ticket(subject: string, body: string, user_id: string) -> {ticket_id, url}</p> <p>Memory strategy<br /> – Short-term (per session): last 10 turns, anonymized entities<br /> – Long-term: none by default; persist resolved FAQs as “suggested macros”<br /> – Tool memory: cache recent order lookups by order_id (5 min TTL)</p> <p>Error handling and retries<br /> – Tool timeouts: 3s read_kb, 2s get_order_status, 5s create_ticket<br /> – Retries: exponential backoff 2 attempts, idempotency keys for write ops<br /> – Fallbacks:<br /> – If read_kb fails → return minimal fallback FAQ<br /> – If get_order_status fails → offer escalation with ticket creation<br /> – If LLM call fails → canned message + queue a “human follow-up” task</p> <p>Security and privacy<br /> – JWT from WP session maps to a short-lived backend token (5 min)<br /> – Tool-level authorization checks (RBAC): support.read_kb, orders.read_own, tickets.create<br /> – PII scrubbing:<br /> – Replace emails/phones with tokens before logging<br /> – Mask order_id except last 4 in user-facing responses<br /> – Prompt guards:<br /> – Block tool calls that include secrets or raw SQL-like input<br /> – Refuse to exfiltrate data not tied to the user</p> <p>Implementation sketch (Python FastAPI)</p> <p>from fastapi import FastAPI, Depends, HTTPException<br /> import httpx, time, uuid</p> <p>app = FastAPI()</p> <p>class ToolError(Exception): pass</p> <p>async def read_kb(query, top_k=4):<br /> # call vector store<br /> async with httpx.AsyncClient(timeout=3) as c:<br /> r = await c.post(“https://vec/search”, json={“q”: query, “k”: top_k})<br /> r.raise_for_status()<br /> return r.json()[“hits”]</p> <p>async def get_order_status(order_id, user_token):<br /> async with httpx.AsyncClient(timeout=2, headers={“Authorization”: f”Bearer {user_token}”}) as c:<br /> r = await c.get(f”https://woo/api/orders/{order_id}”)<br /> if r.status_code == 403:<br /> raise ToolError(“not_authorized”)<br /> r.raise_for_status()<br /> return r.json()</p> <p>async def create_ticket(subject, body, user_id):<br /> idemp = str(uuid.uuid4())<br /> async with httpx.AsyncClient(timeout=5, headers={“Idempotency-Key”: idemp}) as c:<br /> r = await c.post(“https://tickets/new”, json={“subject”: subject, “body”: body, “user_id”: user_id})<br /> r.raise_for_status()<br /> return r.json()</p> <p>async def orchestrate(message, session, user_ctx):<br /> # 1) build tool-available prompt with redacted context<br /> prompt = build_prompt(message, session, user_ctx)<br /> # 2) call LLM with tools<br /> plan = await llm_call_with_tools(prompt)<br /> # 3) validate tool calls<br /> for call in plan.tool_calls:<br /> validate_schema(call)<br /> if call.name == “get_order_status”:<br /> assert user_ctx.scopes.contains(“orders.read_own”)<br /> assert call.args[“order_id”].startswith(user_ctx.allowed_order_prefix)<br /> # 4) execute tools with retries<br /> results = {}<br /> for call in plan.tool_calls:<br /> results[call.id] = await run_with_retry(call)<br /> # 5) final response<br /> final = await llm_finalize(prompt, plan, results)<br /> return final</p> <p>def run_with_retry(call):<br /> async def run():<br /> if call.name == “read_kb”: return await read_kb(**call.args)<br /> if call.name == “get_order_status”: return await get_order_status(**call.args)<br /> if call.name == “create_ticket”: return await create_ticket(**call.args)<br /> raise ToolError(“unknown_tool”)<br /> delay = 0.3<br /> for _ in range(3):<br /> try: return await run()<br /> except (httpx.TimeoutException, ToolError):<br /> await asyncio.sleep(delay); delay *= 2<br /> raise</p> <p>WordPress plugin bridge (minimal)<br /> – Enqueue a chat widget.<br /> – Proxy /wp-json/agent/v1/chat to backend with user JWT.<br /> – Never store API keys in PHP.</p> <p>PHP (excerpt)</p> <p>add_action(‘rest_api_init’, function() {<br /> register_rest_route(‘agent/v1’, ‘/chat’, [<br /> ‘methods’ => ‘POST’,<br /> ‘permission_callback’ => function() { return is_user_logged_in() || true; },<br /> ‘callback’ => ‘aig_chat_proxy’<br /> ]);<br /> });</p> <p>function aig_chat_proxy(WP_REST_Request $req) {<br /> $token = wp_create_nonce(‘aig_session_’ . get_current_user_id());<br /> $body = [<br /> ‘message’ => $req->get_param(‘message’),<br /> ‘session_id’ => aig_get_session_id(),<br /> ‘wp_user’ => get_current_user_id()<br /> ];<br /> $resp = wp_remote_post(‘https://api.aiguy.la/agent/chat’, [<br /> ‘headers’ => [‘X-WP-Token’ => $token],<br /> ‘body’ => wp_json_encode($body)<br /> ]);<br /> return rest_ensure_response(json_decode(wp_remote_retrieve_body($resp), true));<br /> }</p> <p>RAG setup<br /> – Ingest:<br /> – Crawl /docs and /products/*.md<br /> – Chunk at 500–800 tokens with overlap 50<br /> – Store URL slugs and titles for citations<br /> – Retrieval:<br /> – Hybrid (BM25 + vector) to reduce misses<br /> – Filter by product tags if user context includes product_id<br /> – Post-retrieval:<br /> – Deduplicate by URL; keep top_k=5; force at least one “policy” doc when user asks for returns/warranty</p> <p>Guardrails and refusal policy<br /> – If user asks for actions outside scope (refunds, edits to orders):<br /> – Explain limitation and offer to create a ticket with required info<br /> – If confidence low on KB answers:<br /> – Return best-effort summary + references + invitation to escalate</p> <p>Monitoring and analytics<br /> – Capture per-turn:<br /> – user_id (hashed), session_id, tool_calls[], tokens_in/out, latency_ms, confidence, outcome<br /> – Dashboards:<br /> – Deflection rate (answered without ticket)<br /> – First response time vs. baseline<br /> – Tool error rates<br /> – Cost per conversation<br /> – Alerting:<br /> – Spike in get_order_status 403s<br /> – LLM finalize error rate > 2%<br /> – P95 latency > 5s</p> <p>Cost controls<br /> – Use small model for planning; larger for finalize only when confidence < 0.7<br /> – Cache KB responses by canonical question<br /> – Hard cap tokens/session and auto-escalate when reached</p> <p>Evaluation loop<br /> – Weekly batch:<br /> – 100 sampled chats → rubric scoring (accuracy, citation quality, action correctness)<br /> – Auto-generate new tests from real failures<br /> – Synthetic tests:<br /> – Red-team prompts (prompt injection, data exfiltration)<br /> – Tool chaos (forced timeouts) to verify fallbacks</p> <p>Deployment checklist<br /> – [ ] Staging + prod environments with separate keys<br /> – [ ] WP plugin only calls backend; no secrets in WordPress<br /> – [ ] Tool auth scopes enforced server-side<br /> – [ ] Logs PII-scrubbed and encrypted at rest<br /> – [ ] Rate limiting by IP + user + session<br /> – [ ] Runbooks for LLM outage and ticket system outage<br /> – [ ] AB test widget vs. contact form default</p> <p>Example user flow<br /> – User: “Where’s order #1234?”<br /> – Brain:<br /> – Validate session, find user_id matches order prefix<br /> – Call get_order_status<br /> – If success, summarize items + ETA (mask ID)<br /> – If fail 403, offer ticket creation<br /> – Response:<br /> – “Your order ending in 1234 is Shipped via USPS. ETA: Mar 9. Want tracking via email?”</p> <p>What actually ships<br /> – A small WP plugin that renders the chat widget and proxies to an external agent API<br /> – A FastAPI/Express backend that owns tools, auth, and logs<br /> – A vector store for docs<br /> – Observability dashboards<br /> – Guardrails and policies treated as code alongside prompts</p> <p>If you want the minimal viable slice, ship only:<br /> – read_kb + create_ticket<br /> – No order access yet<br /> – Logging + dashboards from day one<br /> Then add get_order_status with strict auth and red-team it before enabling.</p> </div> </article> <article id="post-212" class="post-212 post type-post status-publish format-standard has-post-thumbnail hentry category-ai-agents-chatbots"> <h1 style="margin-bottom: 20px; color: var(--primary);">Building a Production-Ready Support Agent for WordPress (Brain + Hands Architecture)</h1> <div class="entry-content"> <p>This post walks through building a production-grade support agent that runs on your WordPress site. It uses a Brain + Hands architecture, retrieval grounding, safe tool execution, and an ops-friendly deployment you can ship today.</p> <p>Use case<br /> – 24/7 support on a WordPress site<br /> – Answers grounded in your docs, knowledge base, and order/ticket data<br /> – Creates tickets, fetches order status, and escalates when needed<br /> – Auditable, rate-limited, cost-capped, and safe by default</p> <p>Architecture (Brain + Hands)<br /> – Brain: Router and policy LLM. Decides intent, plans steps, calls tools, composes final answer.<br /> – Hands: Strict tools with input/output schemas and timeouts. Examples:<br /> – retrieve_kb(query) → list[doc]<br /> – get_order_status(order_id) → status<br /> – create_ticket(subject, body, user_id) → ticket_id<br /> – wp_lookup_user(email) → user_id<br /> – Memory:<br /> – Short-term: rolling chat window + compact summaries<br /> – Long-term: vector store (pgvector) with namespaces (public docs, private org, tickets)<br /> – Orchestration:<br /> – API service (FastAPI)<br /> – Worker (Celery) for tool calls and long tasks<br /> – Message bus (Redis) + idempotency keys<br /> – Retries with jitter, circuit breakers, dead-letter queue<br /> – Guardrails:<br /> – Tool allowlist by tenant and role<br /> – PII redaction before logging<br /> – Spend caps per session<br /> – Audit log of prompts, tool I/O, and final answer<br /> – Deployment:<br /> – Docker Compose: api, worker, postgres+pgvector, redis, nginx<br /> – Horizontal scale on api/worker<br /> – Observability via OpenTelemetry, Prometheus, and dashboards</p> <p>Minimal data flow<br /> 1) Frontend (WP) posts user_message to /chat.create.<br /> 2) Brain classifies intent and selects tools.<br /> 3) Worker executes tools with timeouts and idempotency.<br /> 4) Brain composes grounded answer, returns JSON.<br /> 5) Logs, metrics, and cost counters are flushed asynchronously.</p> <p>Prompts (policy + tool primer)<br /> System (policy):<br /> – You are a support agent. Only answer using verified sources and allowed tools.<br /> – If retrieval returns low confidence, ask a clarifying question or escalate via create_ticket.<br /> – Never invent order or account details. Use wp_lookup_user and get_order_status.<br /> – Keep answers under 150 words. Provide exact steps when relevant.<br /> – Stop after you complete the task or create a ticket with a summary.</p> <p>Tool primer (append once at session start):<br /> – Tools are authoritative. Handle errors gracefully. On tool failure: retry once with backoff; otherwise escalate.<br /> – Sensitive data: do not echo PII. Replace with [redacted] in responses.</p> <p>Memory plan<br /> – Short-term:<br /> – Keep last 8 turns + rolling summary capped to 1,200 tokens.<br /> – Long-term:<br /> – pgvector schema: docs(id, tenant, chunk, embedding, metadata, updated_at)<br /> – Namespace by tenant. Tag privacy: public | internal.<br /> – Retrieval:<br /> – Hybrid search: BM25 + cosine. Rerank top 50 → top 8 with a small reranker or LLM scoring.<br /> – Attach citations (url/title) to the Brain context. Include only top 3.</p> <p>API skeleton (FastAPI, Python)<br /> – Uses any function-calling LLM client. Replace llm.* with your provider.</p> <p>from fastapi import FastAPI, HTTPException<br /> from pydantic import BaseModel<br /> import uuid, time</p> <p>app = FastAPI()</p> <p>class ChatIn(BaseModel):<br /> session_id: str<br /> user_id: str | None = None<br /> message: str<br /> tenant: str</p> <p>class ToolError(Exception): pass</p> <p>def retrieve_kb(tenant: str, query: str) -> list[dict]:<br /> # 1) embed query 2) pgvector ANN search 3) BM25 4) rerank<br /> # return [{title, url, text, score}]<br /> …</p> <p>def wp_lookup_user(email: str) -> dict | None: …<br /> def get_order_status(order_id: str) -> dict: …<br /> def create_ticket(subject: str, body: str, user_id: str | None) -> dict: …</p> <p>TOOL_REGISTRY = {<br /> “retrieve_kb”: retrieve_kb,<br /> “wp_lookup_user”: wp_lookup_user,<br /> “get_order_status”: get_order_status,<br /> “create_ticket”: create_ticket,<br /> }</p> <p>def call_tool(name, args, idempotency_key: str):<br /> # enforce allowlist, timeouts, and idempotency<br /> # store result keyed by (name, idempotency_key) to avoid duplicates<br /> …</p> <p>def brain_policy(state):<br /> # state: {messages, tenant, memory_summary, budget_left}<br /> # 1) classify intent 2) plan 3) decide tools 4) compose answer<br /> # Use LLM function-calling with tool schema mirroring TOOL_REGISTRY<br /> …</p> <p>@app.post(“/chat.create”)<br /> def chat_create(inp: ChatIn):<br /> session = hydrate_session(inp.session_id, inp.tenant) # memory + budget<br /> plan = brain_policy(session | {“latest_user”: inp.message})<br /> tool_calls = plan.get(“tool_calls”, [])<br /> results = []<br /> for tc in tool_calls:<br /> key = f”{inp.session_id}:{tc[‘name’]}:{uuid.uuid4()}”<br /> try:<br /> res = call_tool(tc[“name”], tc[“arguments”], key)<br /> results.append({“name”: tc[“name”], “result”: res})<br /> except Exception as e:<br /> results.append({“name”: tc[“name”], “error”: str(e)})<br /> answer = brain_compose(session, inp.message, results) # grounded final<br /> log_audit(inp, plan, results, answer)<br /> return {“answer”: answer[“text”], “citations”: answer.get(“citations”, [])}</p> <p>Retries and timeouts<br /> – Default tool timeout: 4s (short I/O) and 15s (ticketing).<br /> – Retries: 1 retry with exponential backoff (200–1200ms jitter).<br /> – Circuit breaker: open after 5 failures/min per tool; auto half-open after 60s.<br /> – Dead-letter: push failed jobs with payload and reason.</p> <p>WordPress integration (minimal plugin)<br /> – Adds a secure endpoint that proxies chat to the API with user/session info.<br /> – Uses WP nonce + JWT for auth. Stores session_id in user meta.</p> <p>add_action(‘rest_api_init’, function () {<br /> register_rest_route(‘aiguy/v1’, ‘/chat’, [<br /> ‘methods’ => ‘POST’,<br /> ‘callback’ => function ($req) {<br /> $user_id = get_current_user_id();<br /> $session_id = get_user_meta($user_id, ‘agent_session’, true);<br /> if (!$session_id) { $session_id = wp_generate_uuid4(); update_user_meta($user_id, ‘agent_session’, $session_id); }<br /> $body = [<br /> ‘session_id’ => $session_id,<br /> ‘user_id’ => $user_id ?: null,<br /> ‘tenant’ => get_bloginfo(‘url’),<br /> ‘message’ => sanitize_text_field($req->get_param(‘message’)),<br /> ];<br /> $resp = wp_remote_post(‘https://api.yourdomain.com/chat.create’, [‘headers’=>[‘Authorization’=>’Bearer ‘.YOUR_JWT],’body’=>wp_json_encode($body),’timeout’=>10]);<br /> return rest_ensure_response(json_decode(wp_remote_retrieve_body($resp), true));<br /> },<br /> ‘permission_callback’ => ‘__return_true’<br /> ]);<br /> });</p> <p>Guardrails and policies<br /> – Tool allowlist per tenant and role. Example: guests cannot call get_order_status.<br /> – PII handling: mask emails, phone numbers in logs; hash user IDs.<br /> – Cost guard: $0.50 per session default cap; block further LLM calls and ask for escalation.<br /> – Content policy: refuse medical/financial advice; offer handoff.</p> <p>Observability<br /> – OpenTelemetry traces: chat.create → brain_policy → tool_calls → compose.<br /> – Events:<br /> – agent.tokens_prompt, agent.tokens_completion<br /> – tool.duration_ms, tool.error_rate<br /> – retrieval.latency_ms, retrieval.disjointness_score<br /> – Dashboards:<br /> – Answer accuracy vs. retrieval overlap<br /> – Ticket creation rate<br /> – Cost per conversation and per tenant</p> <p>Scalability playbook<br /> – API: uvicorn workers = CPU cores * 2; keep-alive 60s.<br /> – Worker: concurrency tuned to I/O bound tasks; start with 2x CPU.<br /> – Cache: L2 cache embeddings and retrieval via Redis; TTL 10m.<br /> – Rate limit: 10 req/min per IP, 60/min per tenant burst with token bucket.<br /> – Batch embeddings and precompute doc chunk vectors in ingestion jobs.</p> <p>Testing checklist<br /> – Golden set of Q&A with expected citations (pass@1 ≥ 0.75)<br /> – Tool chaos tests: 20% forced 500s on get_order_status; verify backoff/handoff<br /> – Latency SLO: p95 ≤ 2.5s without tool calls, ≤ 4.5s with retrieval<br /> – Cost SLO: ≤ $0.03 per turn on average<br /> – Security: ensure tools cannot reach arbitrary URLs; static allowlist only</p> <p>Costs and performance tips<br /> – Use a small reasoning model for policy and a cheaper model for drafting; only upgrade on ambiguity.<br /> – Compress context with structured summaries of past steps.<br /> – Limit retrieval to 8 chunks max, 800 tokens cap.<br /> – Cache rewritten queries and last 3 answers per session.</p> <p>Deployment (Docker Compose)<br /> – Services: api, worker, redis, postgres(pgvector), nginx<br /> – Environment:<br /> – LLM_API_KEY, OPENAI_API_BASE or other provider<br /> – PG_DSN, REDIS_URL<br /> – ALLOWED_TOOLS, COST_CAP_USD<br /> – Rolling updates with zero downtime; keep migrations backward compatible.</p> <p>What to ship first (MVP)<br /> – One tool: retrieve_kb<br /> – One action: create_ticket on low confidence<br /> – Short-term memory<br /> – Token/cost logging<br /> – WordPress proxy endpoint</p> <p>Then iterate:<br /> – Add account/order tools<br /> – Introduce allowlists and spend caps<br /> – Add hybrid retrieval and reranking<br /> – Harden retries and circuit breakers</p> <p>If you want the reference repo with the minimal stack (FastAPI + Celery + pgvector + WP plugin scaffold), reply with “Send the repo link.”</p> </div> </article> <article id="post-190" class="post-190 post type-post status-publish format-standard has-post-thumbnail hentry category-ai-agents-chatbots"> <h1 style="margin-bottom: 20px; color: var(--primary);">Shipping a Brain+Hands Support Agent on WordPress: A Production Blueprint</h1> <div class="entry-content"> <p>This post shows a concrete way to ship a customer support chatbot on WordPress using a Brain+Hands architecture. It focuses on reliable tool use, fast responses, and safe fallbacks—so it works in production, not just demos.</p> <p>High-level goals<br /> – Sub-2s median response on cached answers; proxies to FastAPI with service token.<br /> – Admin settings: API base URL, agent_id, public rate caps, UI messages.</p> <p>System prompt (trimmed)<br /> – “You are the Support Agent for . Objectives: resolve safely, be concise, cite sources if RAG used, never invent order data.<br /> – Tools are authoritative. If a tool returns empty, ask a targeted follow-up.<br /> – Prefer RAG answers for product info; use order API only when the user provides email + order number.<br /> – If confidence {items: [{title, url, snippet, score}]}<br /> – get_order_status(order_id: string, email: string) -> {status, items: […], eta, support_url}<br /> – create_ticket(subject: string, body: string, user_email: string) -> {ticket_id, url}<br /> – wp_get_article(slug: string) -> {title, url, excerpt}<br /> – wp_create_draft(title: string, content: string, tags: [string]) -> {post_id, url}</p> <p>All tools:<br /> – 3 attempts with exponential backoff (100ms, 300ms, 900ms) on 5xx/timeouts<br /> – Per-tool timeouts (1.5–3.0s)<br /> – Circuit breaker after 5 failures/60s; Brain receives tool_unavailable=true<br /> – Input validation + PII redaction in logs<br /> – Idempotency keys for writes (hash of normalized input)</p> <p>RAG design<br /> – Index: product docs, policies, shipping/returns, how-tos<br /> – Chunking: 600–800 tokens with overlap 80; hybrid search (BM25 + vector)<br /> – Metadata: section, updated_at, policy_version, locale<br /> – Freshness: prefer updated_at within 90 days; demote stale content<br /> – Response shaping: cite top 1–2 sources with URLs; never paste long chunks</p> <p>Conversation memory<br /> – Short-term: last 8–12 turns in Redis keyed by session_id<br /> – Long-term: None by default; only store minimal derived facts with TTL (e.g., locale=en-US, product=Model-X)<br /> – PII policy: Do not persist emails/order IDs beyond session TTL unless ticket is created</p> <p>Orchestration logic (pseudo)<br /> – If user intent ∈ {order_status, refund, return} and has required fields -> call appropriate tool<br /> – Else if intent ∈ {product_info, how_to} -> RAG then answer<br /> – Else -> clarify with a single follow-up question<br /> – If total budget > $0.04/turn or total time > 7s -> return concise fallback + offer ticket<br /> – If tool_unavailable -> skip tool path, provide safe guidance, suggest ticket</p> <p>Guardrails<br /> – Schema-enforced tool inputs (pydantic)<br /> – Output moderation on Brain final answer for PII leakage<br /> – Allowlist of domains for citations<br /> – Cost guard: token + tool meter per session; degrade to smaller model if exceeded<br /> – Red-team prompts stored as regression tests</p> <p>Prompt templates (snippets)<br /> – Tool-use meta instruction: “Before calling a tool, state your intent in one sentence. After tool result, summarize and answer. Do not call the same tool twice with identical params.”<br /> – Clarifier: “Ask exactly one targeted question if required fields are missing.”<br /> – Citation rule: “If using RAG, include ‘Sources: ’ on one line.”</p> <p>Error handling patterns<br /> – Tool 4xx: user-correctable -> ask for missing fields<br /> – Tool 5xx/timeout: retry; if still failing -> graceful degradation path + ticket option<br /> – JSON parse errors: re-ask model with constrained tool schema and lower temperature<br /> – Hallucination guard: if tool not called where required -> reject answer and replan</p> <p>Performance tuning<br /> – Use smaller model for planning (e.g., 4o-mini) and larger model for final answer only when needed<br /> – Semantic cache: cache final answers for RAG-only turns with TTL 24h and versioned by doc hashes<br /> – Parallelize independent tools (e.g., FAQ + inventory check) with a 2.5s overall soft budget<br /> – Stream tokens to frontend; show “retrieving order…” status on tool calls</p> <p>Deployment<br /> – FastAPI behind API Gateway + Lambda or ECS Fargate<br /> – Postgres + pgvector on RDS; Redis on ElastiCache<br /> – CI/CD: run unit tests for tools, contract tests, and evaluation suites before deploy<br /> – Observability: traces per turn, tool-level spans, model cost metrics, drop rates, p95 latency</p> <p>Evaluation suite<br /> – 50–100 scripted conversations covering: missing-order-id, stale-RAG, tool-500, policy-edge, refund vs exchange<br /> – Metrics: exactness (domain rubric), tool accuracy, escalation rate, latency, cost/turn<br /> – Canary: 5% live traffic for 48h with auto-rollback thresholds</p> <p>Minimal FastAPI skeleton (abridged)<br /> – POST /chat<br /> – Validate session_id, rate limit<br /> – Load context + RAG if needed<br /> – Call Brain (choose model based on budget)<br /> – Execute tools via router with retries<br /> – Stream final tokens<br /> – Log trace + metrics</p> <p>Security notes<br /> – Service-to-service auth (JWT) between WP and FastAPI<br /> – Do not expose vendor API keys to the browser<br /> – Encrypt all logs at rest; redact PII<br /> – WordPress nonce for front-end requests</p> <p>When to escalate<br /> – Low confidence + sensitive requests (refund exceptions, legal)<br /> – Repeated tool failures<br /> – High-friction tasks better handled by humans<br /> – Provide ticket link and SLA</p> <p>Outcome<br /> This blueprint is the shortest path we’ve found to a reliable, fast, and safe support agent on WordPress. Start with RAG-only answers, then add high-value tools with strict contracts and clear fallbacks. Measure everything.</p> </div> </article> <article id="post-53" class="post-53 post type-post status-publish format-standard has-post-thumbnail hentry category-ai-agents-chatbots"> <h1 style="margin-bottom: 20px; color: var(--primary);">AI Agents & Chatbots Overview</h1> <div class="entry-content"> <p>AI agents and chatbots are software systems that simulate conversation and perform tasks using natural language. They can be rule-based or use machine learning models such as large language models (LLMs) to understand user input, maintain context, and generate relevant responses. As they evolve, chatbots are being deployed across websites and messaging platforms to provide customer service, lead generation, scheduling, and more.</p> <p>First, an agent collects a user question or command and classifies the intent using natural language understanding. It then retrieves information from knowledge bases or connected applications (CRM, calendars, email). Modern chatbots also use generative models to craft personalized responses rather than generic scripted answers. By connecting to APIs, these agents can perform tasks such as placing orders, booking appointments, or updating records.</p> <p>Building an effective chatbot requires careful planning. Start by defining your goals and audience: do you want a lead-generation bot, a support assistant, or a general FAQ chatbot? Map out key user journeys and decide which tasks can be automated. Then design conversation flows with fallback options for ambiguous questions. If using generative AI, consider guardrails to prevent off-topic or unsafe responses.</p> <p>Integration is essential. Chatbots are most valuable when connected to CRM systems, calendars, email platforms, and databases. For example, a real-estate chatbot can automatically log inquiries in a CRM, send property details from a Google Sheet, and schedule viewings on a calendar. APIs and automation platforms make it easier to orchestrate these flows.</p> <p>When deploying chatbots, test them extensively with real users. Iterate on the design based on conversation logs. Train models on domain-specific data to improve understanding of industry terminology. Provide a way for users to speak to a human if needed. Monitor analytics such as resolution rate, drop-off points, and conversion metrics to continuously optimize.</p> <p>As generative AI becomes more powerful, chatbots are evolving from simple question-answering tools to proactive digital assistants. With retrieval-augmented generation, agents can search live data sources and compose answers on the fly. They can summarize long documents, rewrite text for different reading levels, and generate personalized marketing copy. Combined with speech synthesis, they can serve as voice assistants.</p> <p>Security and ethics are also important. Ensure that sensitive data (personal information, financial details) is handled securely and that chatbots do not share private information. Provide transparency that users are interacting with an AI. Use bias-mitigation techniques to prevent discriminatory responses. Comply with relevant regulations such as GDPR and the California Consumer Privacy Act (CCPA).</p> <p>In summary, AI agents and chatbots offer tremendous potential to automate workflows, improve customer experiences, and scale service delivery. By planning thoughtfully, integrating with existing systems, and leveraging the latest advances in conversational AI, businesses can deploy chatbots that drive engagement and efficiency while lea</p> <p>ving human staff free to focus on high-value tasks.</p> </div> </article> <article id="post-25" class="post-25 post type-post status-publish format-standard has-post-thumbnail hentry category-ai-agents-chatbots"> <h1 style="margin-bottom: 20px; color: var(--primary);">AI Agents & Chatbots Overview</h1> <div class="entry-content"> <p>Deploying a chatbot on your website or connecting it to your CRM can dramatically reduce manual work. An agent can greet new visitors, capture lead information, book appointments and send smart follow‑ups automatically. For service requests, chatbots can route inquiries to the right department or perform actions such as checking order status and updating account details. They can also deliver personalized recommendations by integrating with product catalogs and user data. When paired with email or SMS automation, agents ensure that your communications are timely and consistent, improving customer satisfaction and conversion rates.</p> <p>Designing an effective AI agent requires thoughtful planning. Start by outlining the primary goals—are you trying to answer frequently asked questions, schedule appointments or qualify leads? Use clear, friendly language and provide quick reply buttons to guide users. Incorporate fallback responses when the agent doesn’t understand a query, and always offer an option to connect with a human. Continuous improvement is crucial: analyze chat logs to identify gaps, refine training data and update your flows. As the technology evolves, consider adding voice interfaces or connecting your agent to multiple channels like Facebook Messenger, Slack and WhatsApp. By investing in a well‑designed chatbot, you’ll create a scalable touchpoint that works for you 24/7.</p> <p>Another advantage of modern AI agents is their ability to learn from interactions. With access to transcripts, you can identify common pain points and adjust your product or service accordingly. You can also use chatbots to conduct surveys or gather feedback at the end of an interaction. For sales teams, an agent can qualify leads by asking targeted questions, scoring them based on responses and updating your CRM automatically. This ensures your team spends time on the most promising opportunities.</p> <p>When integrating AI agents into your workflow, security and privacy must be top priorities. Use secure authentication methods when connecting to your CRM, scheduling system or payment gateway. Limit the data your bot collects to what’s necessary and be transparent with users about how their information will be used. Finally, remember that a chatbot should complement, not replace, your human team. Offering a seamless handoff to a person when needed creates trust and ensures complex issues are handled appropriately. With a strategic approach, AI agents become not just a tool but a key partner in scaling your business.</p> <p>By embedding an AI chatbot on your website or connecting it to your CRM, you can capture leads, book appointments and send smart follow‑ups automatically. Chatbots can be trained to provide tailored answers, route inquiries to the right person and summarise interactions.</p> <p>For small businesses, AI agents free up time and reduce response delays. They are perfect for handling frequently asked questions, qualifying prospects or generating meeting summaries so you can focus on higher‑value work.</p> </div> </article> </div> </section> <footer> <div class="container"> <div class="footer-grid"> <div class="footer-col"> <h4>GravityExpert</h4> <p>Based in Westwood, Los Angeles.<br>Solving WordPress data problems since 2014.</p> </div> <div class="footer-col" style="text-align: right;"> <h4>Quick Links</h4> <ul> <li><a href="https://aiguyinla.com/plugin">Anti-Spam Addon</a></li> <li><a href="https://aiguyinla.com/services">Custom Development</a></li> </ul> </div> </div> <div class="copyright"> © 2026 Ai Guy in LA. All rights reserved. </div> </div> </footer> <script> document.addEventListener('DOMContentLoaded', function() { const toggleBtn = document.getElementById('mobile-toggle'); const navMenu = document.getElementById('mobile-menu'); if(toggleBtn && navMenu) { toggleBtn.addEventListener('click', function() { // Toggle the 'active' class on both the button and the menu toggleBtn.classList.toggle('active'); navMenu.classList.toggle('active'); }); } }); </script> <script type="speculationrules"> {"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/gravity-expert-theme/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]} </script> <script id="wp-emoji-settings" type="application/json"> {"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"concatemoji":"https://aiguyinla.com/wp-includes/js/wp-emoji-release.min.js?ver=6.9.4"}} </script> <script type="module"> /* <![CDATA[ */ /*! This file is auto-generated */ const a=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=a,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\u1fac8")}return!1}function f(e,t,n,a){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),s=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(e=>{s[e]=t(o,e,n,a)}),s}function r(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}a.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),c.toString(),p.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=e=>{i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const n in e)a.supports[n]=e[n],a.supports.everything=a.supports.everything&&a.supports[n],"flag"!==n&&(a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&a.supports[n]);var t;a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&!a.supports.flag,a.supports.everything||((t=a.source||{}).concatemoji?r(t.concatemoji):t.wpemoji&&t.twemoji&&(r(t.twemoji),r(t.wpemoji)))}); //# sourceURL=https://aiguyinla.com/wp-includes/js/wp-emoji-loader.min.js /* ]]> */ </script> </body> </html>