Skip to main content

Knowledge > Products > Chatbot > Architecture

Chatbot Architecture

Request Pipeline

Every chatbot message flows through a single POST /api/chatbot/stream handler. The pipeline has 16 stages, each of which can short-circuit and return early.

Pipeline Pseudocode

POST /api/chatbot/stream

INPUTS: { message, churchId, sessionId, history?, agentType?, lensOverride?, lensNameOverride? }

1. RATE LIMIT CHECK
limiter(ip) — 30 requests per 60 seconds per IP
→ 429 if exceeded

2. VALIDATE REQUEST
- message, churchId, sessionId are required
- message.length <= 2000 characters
- At least one LLM provider key (ANTHROPIC_API_KEY or OPENAI_API_KEY) must be set
→ 400 / 503 on failure

3. ORIGIN VALIDATION
- Extract origin from Origin header or Referer
- Check against trusted origins whitelist:
churchwiseai.com, sermonwise.ai, sharewiseai.com
+ localhost:3000/3001/3002 in development
- Log suspicious origins (non-trusted, non-custom-domain)
- Log requests with no origin at all
- NOTE: Does not hard-block unknown origins (churches embed on arbitrary domains)

4. CHURCH VALIDATION (early gate)
- Query premium_churches: churchId must exist, chatbot_enabled=true, status IN ('active', 'preview')
- This runs BEFORE any expensive work (RAG, LLM calls) to prevent abuse
→ 404 if church not found or chatbot disabled

5. MODERATION CHECK
checkRestriction(churchId, sessionId)
- Checks moderation_restrictions table for active cooldown/temp_block/permanent_block
→ Returns restriction message if restricted (200 with restricted=true)

6. FAQ MATCHING (fast path)
matchFAQ(message, churchId, agentType)
- Exact match with exact_response=true → return immediately (zero LLM cost, does not count toward usage limits)
- Fuzzy match → save as faqPreferredContext for LLM injection
- Failure is non-fatal (logged, pipeline continues)

7. LOAD CHURCH DATA
- Query churches table: name, denomination, address, phone, website, working_hours
- Query premium_churches: plan, custom_name, custom_hours, custom_staff, custom_ministries,
what_to_expect, cap_info, chatbot_enabled
→ 404 if church not found; 403 if chatbot not enabled

8. CHECK USAGE LIMITS
normalizePlanTier(plan) → starter | pro | suite
checkUsageLimit(churchId, tier)
- Monthly LLM message limits: starter=200, pro=1000, suite=5000
- Canned responses do NOT count
→ Returns limitReached message if over limit (200 with limitReached=true)

9. LOAD CONFIG
- Query church_voice_agents: pastor_name, callback settings, Cal.com keys,
sermon topic/series/verse, announcements, giving config
- Query organization_settings: agent_tool_config, agent_config, chatbot_config
- Detect chatbot source:
'pewsearch_auto_provision' → basic chatbot (Q&A only)
plan='pro_website' and not basic → Pro Website chatbot (restricted)
else → full chatbot (agentic, all tools)

10. RESOLVE THEOLOGICAL LENS
Priority chain:
(1) Client-side override (lensOverride + lensNameOverride, validated: ID 1-17, name <= 50 chars)
(2) Church setting in church_theological_lenses table
(3) Denomination auto-detect via DENOMINATION_TO_LENS mapping
(4) Default: Christocentric (lens ID 10)

Then fetch:
- Doctrinal rules from theological_contradictions (must-include/must-exclude terms per doctrine)
- Church-specific doctrinal overrides from organization_settings
- Lens vocabulary from lens_knowledge (preferred/avoided terminology)

11. RAG RETRIEVAL (parallel)
generateEmbedding(message) — OpenAI text-embedding-3-small (1536 dims)
Promise.all([
searchRAG(embedding, lensIds=[lensId], matchCount=8), // theological content
searchChurchKnowledge(churchId, embedding), // church-specific KB
])
- Also loads product_knowledge table (shared billing/feature info)
- Failure is non-fatal (logged, pipeline continues without RAG)

12. BUILD CONTEXT BLOCKS
Assembles these blocks into the system prompt:
- Church facts (name, address, denomination, phone, website, hours, staff, ministries, what to expect)
- Church knowledge base (church-specific FAQs and documents — highest priority)
- Theological RAG (curated content from unified_rag_content)
- Product knowledge (PewSearch/ChurchWiseAI feature info)
- FAQ preferred context (fuzzy-matched FAQ, if any)
- Doctrinal rules (must-include/must-exclude per doctrine)
- Lens vocabulary (tradition-specific word choices)
- Critical local resources (pre-fetched emergency numbers, CAP info)
- Contact info (church phone, website)

13. ROUTE TO CHATBOT TYPE
Three branches diverge here based on chatbot source detection from step 9:

BASIC CHATBOT (PewSearch auto-provision):
- Simple Q&A receptionist prompt, 1 tool (prayer request only)
- Max 300 tokens, temperature 0.3
- Scope: church facts only, redirect off-topic, upsell ChurchWiseAI after prayer

PRO WEBSITE CHATBOT:
- Richer than basic, still restricted vs full
- 1 tool (prayer request only), theology-aware
- Upsells advanced tools without saying "upgrade" or "pay more"
- Max 400 tokens, temperature 0.3, up to 2 tool-use rounds

FULL CHATBOT:
- Full HEAR protocol, all enabled tools filtered by agent type
- Agent specialization layer (persona, domain ruleset, personality, handoff rules)
- Dynamic tool filtering, temperature/max_tokens from agent config
- Up to 3 tool-use rounds

14. FULL CHATBOT: LLM TOOL-USE LOOP
(See "Two-Call LLM Pattern" below for detail)

resolve agent config (personality overrides, handoff rules)
build agent system prompt (base + persona + domain ruleset + personality + tool instructions + safety)
filter tools for agent type (TOOL_AGENT_MAP)
determine escalation (shouldEscalate — checks for crisis, complexity, emotional depth)

for round = 0 to MAX_ROUNDS (3):
callLLM(systemPrompt, messages, tools=(if round < MAX_ROUNDS), temperature, maxTokens, escalate)
if response.toolCalls.length > 0 and round < MAX_ROUNDS:
for each tool call:
executeTool(name, args, context) → string result
log to tool_invocations (fire-and-forget)
append assistant message with tool_use blocks
append user message with tool_result blocks
continue
if response.text:
finalText = response.text
break
// Empty text fallback chain:
retry with clean messages (no tool artifacts)
retry with escalated model (Sonnet)
crisis-specific or conversational fallback text

15. CRISIS SAFETY NET (post-processing)
NON-NEGOTIABLE — runs on ALL chatbot types (basic, pro_website, full)
if message matches self-harm/crisis regex patterns:
if response missing any of [988, 741741, 911]:
auto-append full crisis resource block
if LLM did not call flag_safety_concern:
auto-execute flag_safety_concern(level='urgent') as system_safety_net
log violation and auto-escalate (moderation system)

16. TRACK USAGE + RETURN RESPONSE
trackUsage(churchId, sessionId, responseSource, tokens, model)
upsert chatbot_conversations (fire-and-forget)
increment_conversation_counts via RPC (fire-and-forget)
log to response_reviews for admin training review (fire-and-forget)
return JSON response

Two-Call LLM Pattern

The chatbot uses a multi-round tool-use loop (up to 3 rounds for full chatbot, 2 for Pro Website):

Round 0: LLM call WITH tool definitions
→ Model returns tool_use blocks (e.g., submit_prayer_request, get_church_directions)
→ Execute tools, embed results as tool_result blocks in message history
Round 1: LLM call WITH tool definitions (may call more tools)
→ Execute additional tools if requested
Round 2: LLM call WITH tool definitions (final tool round)
→ Last chance for tool calls
Round 3: LLM call WITHOUT tool definitions (forced text)
→ Model must produce text response using all accumulated tool results

Each round preserves the full conversation state. The assistant message includes both text fragments and tool_use blocks. Tool results come back as tool_result blocks referencing the tool_use_id from the preceding assistant message. This structured format is required by Anthropic's API.

The loop exits early when the model returns text without requesting tools. Most conversations complete in 1-2 rounds.

Empty Text Recovery

If the LLM returns empty text (a known edge case when Haiku processes tool_result artifacts without tool definitions), the system follows a three-step recovery:

  1. Retry with clean messages -- strips tool_use/tool_result artifacts, re-sends original history + current message
  2. Escalate to Sonnet -- more capable model handles short follow-ups better
  3. Hardcoded fallback -- crisis-specific text (with mandatory resources) if crisis detected, otherwise a conversational prompt to continue

Error Handling

All errors are caught at the top level of the POST handler:

  • Errors are logged to console and reported via reportError() to the ops-reporter system
  • A generic user-facing message is returned: "Sorry, something went wrong. Please try again." with HTTP 500
  • Non-fatal failures (RAG, FAQ matching, product knowledge, doctrinal rules, lens vocabulary, tool logging, conversation tracking) are caught individually and logged as warnings without aborting the pipeline
  • Fire-and-forget operations (usage tracking, conversation upserts, response review logging, tool invocation logging) use .catch() to prevent unhandled rejections

Response Format

Standard Response

{
"response": "The chatbot's text response",
"model": "claude-haiku-4-5-20251001",
"provider": "anthropic",
"rag": {
"theological_hits": 5,
"church_kb_hits": 2,
"embedding_generated": true,
"faq_matched": false
}
}

FAQ Short-Circuit Response

{
"response": "The canned FAQ answer",
"source": "canned",
"cannedResponseId": "uuid"
}

Basic / Pro Website Response

{
"response": "Response text",
"source": "basic_chatbot" | "pro_website",
"upgradeUrl": "https://churchwiseai.com/pricing",
"upgradeMessage": "Unlock All 33 Ministry Tools"
}

Moderation Restricted Response

{
"response": "You've been temporarily restricted...",
"restricted": true,
"restriction_type": "cooldown" | "temp_block" | "permanent_block",
"expires_at": "2026-03-25T12:00:00Z"
}

Usage Limit Response

{
"response": "Monthly conversation limit reached...",
"limitReached": true,
"limit": 200,
"used": 200
}

Error Response

{
"error": "Sorry, something went wrong. Please try again."
}

Key Design Decisions

Why One Endpoint, Not Separate Routes

All chatbot types (basic, pro_website, full) share the same validation, moderation, church loading, and crisis safety net logic. Splitting into separate routes would duplicate safety-critical code. The internal branching happens at step 13 after all shared checks are complete.

Why Fire-and-Forget for Tracking

Usage tracking, conversation logging, response reviews, and tool invocations are non-critical for the user experience. Making the user wait for database writes would add 50-200ms latency per message. These operations use Promise.resolve().catch() to execute asynchronously without blocking the response.

Why Regex Crisis Detection, Not Just LLM

LLMs occasionally omit crisis resources despite explicit system prompt instructions. The regex-based safety net is a deterministic backstop that cannot be prompt-injected, cannot hallucinate away resources, and cannot be defeated by model behavior changes. It runs on every response to every chatbot type. This is non-negotiable.

Why Anthropic Primary, Not OpenAI

Claude Haiku 4.5 provides better tool-use reliability, more consistent persona adherence, and superior empathetic language for pastoral care contexts compared to gpt-4o-mini at a comparable price point. OpenAI serves as an automatic fallback if Anthropic experiences downtime. Provider failover triggers an email alert (rate-limited to 1 per 15 minutes).

Database Dependencies

TableAccess PatternPurpose
premium_churchesReadChatbot enablement, plan tier, church config, CAP info
churchesReadChurch facts (name, address, denomination)
church_voice_agentsReadPastor name, Cal.com config, sermon info, giving config
organization_settingsReadTool config, agent config, chatbot source, doctrinal overrides
church_theological_lensesReadChurch's theological lens assignment
sai_theological_lensesReadLens name lookup
theological_contradictionsReadDoctrinal must-include/must-exclude rules
lens_knowledgeReadTradition-specific vocabulary
unified_rag_contentRead (vector search)Theological RAG content
church_knowledge_baseRead (vector search)Church-specific knowledge
product_knowledgeReadShared product/billing info
church_local_resourcesReadCritical local emergency resources
chatbot_conversationsRead/WriteConversation tracking, message counts
chatbot_usageWritePer-message token/cost tracking
tool_invocationsWriteTool execution audit log
response_reviewsWriteLLM response logging for admin review
moderation_violationsRead/WriteViolation logging and restriction checks
moderation_restrictionsReadActive restriction lookup
chatbot_faqsReadFAQ matching

See Also

  • Overview -- product summary, pricing, current state
  • Agents -- agent specialization, persona templates, domain rulesets
  • Moderation -- violation types, escalation ladder, restriction logic