Built-in AI chatbot (no EE, uses Llm::Resolver) that answers in-scope chats
from a knowledge base and hands off to a human when needed. Selected per
inbox via an Integrations::Hook with app_id 'chatbot'.
- Integrations::Chatbot::ProcessorService (mirrors Dialogflow/Captain)
wired via HookListener + HookJob + apps.yml(chatbot, inbox)
- Chatbot::DecisionService: 1-call default ({in_scope/refuse/handoff}),
2-call option; thread-safe prompt threading
- Chatbot::KnowledgeRetriever: keyword top-k over KB (+ embedding reserved)
- KnowledgeBaseFaq + import service (md per-heading + front-matter tags)
- Chatbot::ConfigService + admin chatbot_config endpoint
- refs off-topic (e.g. fortune-telling); handoff = bot_handoff! (pending->open)
87 lines
3.4 KiB
Ruby
87 lines
3.4 KiB
Ruby
# Per-account chatbot configuration for the self-contained OSS chatbot.
|
|
#
|
|
# Reads/writes the account's chatbot settings in Account#custom_attributes (jsonb).
|
|
# The chatbot processor + decision service read from here so there's a single source
|
|
# of truth for how the bot behaves for a given account.
|
|
#
|
|
# Keys (namespaced 'chatbot_*'):
|
|
# chatbot_enabled [bool] gates whether the chatbot replies (default false)
|
|
# chatbot_system_prompt [String] the persona/system instructions for the answer LLM
|
|
# chatbot_guardrail_prompt [String] the allowed-scope / guardrail instructions
|
|
# chatbot_out_of_scope_reply [String] templated refusal reply for out-of-scope messages
|
|
# chatbot_call_mode [Integer] 1 (one call) or 2 (guardrail + answer) — default 1
|
|
#
|
|
# Guardrail + out-of-scope reply have safe Thai defaults; system prompt and call mode
|
|
# fall back to built-in values when unset.
|
|
module Chatbot::ConfigService
|
|
KEYS = %w[
|
|
chatbot_enabled chatbot_system_prompt chatbot_guardrail_prompt
|
|
chatbot_out_of_scope_reply chatbot_call_mode
|
|
].freeze
|
|
|
|
DEFAULT_SYSTEM_PROMPT = <<~PROMPT.freeze
|
|
You are a helpful customer-service assistant for this business. Answer using ONLY the
|
|
provided knowledge base and conversation history. Be concise, accurate and polite.
|
|
If the knowledge base does not contain the answer, say you are not sure and offer to
|
|
connect the customer with a support agent. Do not invent facts.
|
|
PROMPT
|
|
|
|
DEFAULT_GUARDRAIL_PROMPT = <<~PROMPT.freeze
|
|
The bot answers questions about this business's PRODUCTS, SERVICES, and related support
|
|
topics only. It must NOT answer unrelated or off-topic requests (e.g. personal advice,
|
|
fortune-telling, horoscopes, unrelated general knowledge).
|
|
PROMPT
|
|
|
|
DEFAULT_OUT_OF_SCOPE_REPLY = 'ขออภัยครับ คำถามนี้อยู่นอกขอบเขตที่เราสามารถให้บริการได้ กรุณาสอบถามเรื่องสินค้าและบริการของเรา'
|
|
|
|
module_function
|
|
|
|
def enabled?(account)
|
|
account.custom_attributes['chatbot_enabled'] == true
|
|
end
|
|
|
|
def system_prompt(account)
|
|
value_or_default(account, 'chatbot_system_prompt', DEFAULT_SYSTEM_PROMPT)
|
|
end
|
|
|
|
def guardrail_prompt(account)
|
|
value_or_default(account, 'chatbot_guardrail_prompt', DEFAULT_GUARDRAIL_PROMPT)
|
|
end
|
|
|
|
def out_of_scope_reply(account)
|
|
value_or_default(account, 'chatbot_out_of_scope_reply', DEFAULT_OUT_OF_SCOPE_REPLY)
|
|
end
|
|
|
|
def call_mode(account)
|
|
configured = account.custom_attributes['chatbot_call_mode'].to_i
|
|
[1, 2].include?(configured) ? configured : 1
|
|
end
|
|
|
|
# Apply a params hash of allowed keys to the account's custom_attributes and persist.
|
|
# Returns the resulting config hash. Ignores/merges only known keys.
|
|
def update!(account, params)
|
|
attrs = account.custom_attributes || {}
|
|
KEYS.each do |key|
|
|
attrs[key] = params[key] if params.key?(key)
|
|
end
|
|
account.update!(custom_attributes: attrs)
|
|
config(account)
|
|
end
|
|
|
|
# @return [Hash] full current config
|
|
def config(account)
|
|
{
|
|
enabled: enabled?(account),
|
|
system_prompt: system_prompt(account),
|
|
guardrail_prompt: guardrail_prompt(account),
|
|
out_of_scope_reply: out_of_scope_reply(account),
|
|
call_mode: call_mode(account)
|
|
}
|
|
end
|
|
|
|
def value_or_default(account, key, default)
|
|
value = account.custom_attributes[key]
|
|
value.presence || default
|
|
end
|
|
end
|