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