ตอนที่เรากำลังมองหา omni-channel chatbot สำหรับ https://cafn.co ซึ่งเป็น coffee marketplace ของเรา สิ่งแรกที่ต้องการคือ open source เพราะอยากปรับแต่ง workflow และ automate ได้ตามใจ
เครื่องมือ omni-channel open-source ส่วนใหญ่ใช้โมเดล freemium คือฟรีพื้นฐาน แต่ฟีเจอร์สำคัญต้องจ่ายเงิน จนมาเจอ Raven ซึ่งเป็น messaging platform open-source ที่สร้างบน Frappe Framework หน้าตาดี รองรับทั้ง web และ mobile มี AI ในตัว และไม่มี paid tier เลย ซึ่งตรงกับแนวทางที่ Frappe และ ERPNext ยึดถือมาตลอด
พอคิดดูแล้ว omni-channel chat มันก็แค่ chat app ที่รับ-ส่งข้อความจากหลาย channel ภายนอก ไม่น่าซับซ้อนอะไรมากใช่มั้ย?
เราเลยลงมือเพิ่ม omni-channel chat support เข้าไปใน Raven ความคิดแรกคืออยากสร้างเป็น app แยก แต่สุดท้ายตัดสินใจไม่ทำแบบนั้น
ทำไมไม่สร้างเป็น App แยก
- Raven doctypes ต้องแก้ไข เพื่อรองรับ omni-channel ถ้าแยก app ออกมาก็ต้องทำ monkey-patching หรือ fork Raven อยู่ดี
- UI ต้องปรับแต่งหนัก ทั้งฝั่ง web และ mobile ของ Raven
- Omni-channel กับ messaging ใช้ logic เดียวกันเป็นส่วนใหญ่ เพราะทั้งคู่คือ chat app ที่รับและส่งข้อความ การแยกเป็นสอง app จะทำให้ต้อง duplicate utilities และ functions โดยไม่ได้ประโยชน์อะไรเพิ่ม
นิยามของ Omni Channel Chat
Omni-channel chat คือการมี UI กลางที่เดียว ให้ทีมจัดการบทสนทนาจากหลาย channel พร้อมกัน ไม่ว่าจะเป็น WhatsApp Business, LINE OA, Facebook Messenger หรือช่องทางอื่น
สิ่งที่ไม่ครอบคลุมคือการเชื่อม Microsoft Teams เข้า Raven หรือการผูก LINE ส่วนตัว scope ของเราจำกัดเฉพาะ official business channel ที่ไหลเข้า inbox กลางสำหรับทีม
Overview Architecture
มีสี่ component หลัก
OmniChannelProviderConfig
Frappe doctype ที่เก็บ API key และ secret ของแต่ละ chat provider ทำหน้าที่เป็น configuration layer ที่ provider class ใช้อ่านข้อมูล
OmniChannelProvider
Interface สำหรับโต้ตอบกับ chat provider แต่ละเจ้าผ่าน standard message format
มีหน้าที่ดังนี้:
- Inbound: รับ raw message จาก provider แปลงเป็น standard message แล้วส่งต่อให้ callback ที่ลงทะเบียนไว้ (ในกรณีของเราคือส่งเข้า Raven ผ่าน OmniChannelRavenConnector)
- Outbound: expose interface สำหรับส่ง standard message ออกไปยัง provider
Simplified code:
class OmniChannelProvider(ABC):
@abstractmethod
def handle_webhook(self, callback: Callable):
"""Extract data from incoming web hook."""
@abstractmethod
def send_message(self, destination_id: str, std_message: StdMessage):
"""Send an outbound message."""
# ...(and more)OmniChannelRavenConnector
Interface ที่เชื่อม standard message layer กับระบบ Raven
- Inbound: รับ standard message แล้วบันทึกเป็น Raven Message doctype พร้อมสร้าง doctype ที่เกี่ยวข้องถ้ายังไม่มี เช่น Raven Workspace, Raven Channel
- Outbound: ดักจับ Raven message doctype แปลงเป็น standard message แล้วส่งให้ provider
Simplified code:
class OmniChannelRavenConnector:
def handle_inbound(self, destination_id: str, sender_id: str, std_message: StdMessage):
# ...(saving msg into Raven Message)
def handle_outbound(self, raven_message: RavenMessage):
# ...(convert Raven Message into standard message and push to provider)Standard Message
Format กลางสำหรับ message ที่ทุก provider implementation ใช้ร่วมกัน แต่ละ message มี method to_provider ที่ provider เรียกเพื่อแปลงเป็น format ของตัวเอง
Simplified code:
class StdMessage(ABC):
@property
@abstractmethod
def provider_mapping(self) -> dict["ProviderType", Callable[[], dict]]:
"""Mapping of provider to a callable that converts the message to the provider format."""
def to_provider(self, provider_type: "ProviderType") -> Any:
if provider_type not in self.provider_mapping:
raise NotImplementedError("Provider not implemented.")
return self.provider_mapping[provider_type]()
class TextMessage(StdMessage):
def to_line(self):
return LineTextMessage(text=self.text, sender=self.build_line_sender())
def to_facebook(self):
return {"text": self.text}
@property
def provider_mapping(self):
return {
"line": self.to_line,
"facebook": self.to_facebook,
}ขั้นตอนในการรองรับ Provider ใหม่
- เพิ่ม field ของ provider นั้นใน
OmniChannelProviderConfig - สร้าง provider class ใหม่ที่ inherit จาก abstract class ของ provider
- อัปเดต method
to_providerใน base standard message ถ้าจำเป็น
แล้ว AI ล่ะ?
บอกว่าอยากสร้าง chatbot แต่ทำไมสุดท้ายถึงมาสร้าง omni-channel chat แทน?
การให้ AI ตอบ message ที่เข้ามาเป็นส่วนที่ง่ายจริง ๆ แต่พอจะทำให้ production-ready มันซับซ้อนขึ้นทันที
- Chat history - ต้องจัดการและส่ง conversation history ให้ AI ทุก request ถ้าไม่มี AI จะเห็นแค่ข้อความล่าสุดและตอบโดยไม่มี context ซึ่งทำให้แทบไม่มีประโยชน์เลยในการสนทนาจริง
- Bot permissions และ context - ต้องกำหนดขอบเขตว่า bot เข้าถึงและทำอะไรได้บ้าง Frappe ช่วยได้ในส่วนนี้เพราะใช้ระบบ doctype permission ในการกำหนดสิ่งที่ bot session เข้าถึงได้ แต่ก็ยังต้องคิดให้ดีว่าจะ expose tools และ context อะไรให้บ้าง
การสร้าง omni-channel ก่อนทำให้ AI มี message pipeline ที่เชื่อถือได้รองรับอยู่แล้ว เมื่อข้อความจากทุก channel ไหลเข้าระบบเดียวอย่างสะอาด การเพิ่ม AI response เข้าไปก็กลายเป็นปัญหาที่ง่ายและจัดการได้มากขึ้น
วิธีใช้งาน
app ยังอยู่ในช่วง beta อยู่ อาจมีบางส่วนที่ยังไม่เสถียร
- ติดตั้ง SpaceCode | Raven app
- เพิ่ม Raven Workspace แล้วติ๊ก
Is Omni-Channel Workspace - เพิ่ม Omni-Channel Chat Provider
- เสร็จแล้ว!
App demo

Use Cases
- ใช้ AI ตอบคำถาม FAQ ด้วย vector search
- เพิ่ม AI workflow สำหรับจัดการการจองของลูกค้า
- ใช้ AI ที่ integrate กับ ERPNext ตอบคำถามเรื่องราคาแบบ real time
Source Code
ดู source code ทั้งหมดได้บน GitHub สามารถ explore, fork หรือ contribute ได้เลย thspacecode/raven