跳到主要内容

Chatwoot(Captain)— 架构与原理

30 秒导读: Chatwoot 是一个开源客服平台(对标 Intercom/Zendesk)。本文档讲它的全渠道接入,而是聚焦它的 AI 大脑 Captain:当客户在网站聊天框/邮件/WhatsApp 里发来一条消息,Captain 怎么用"知识库检索 + 多 agent 协作 + 工具调用"自动答复,答不上来又怎么干净利落地转给人类坐席。

1. 这是什么(零基础也能懂)

一句话定义: Chatwoot 是一个把所有客户对话汇总到一个收件箱的客服系统;Captain 是它内置的 AI 客服 agent,能自动读知识库、回答常见问题、必要时转人工。

解决谁的什么问题:

假设你开了家公司,客户从网站聊天框、邮件、Instagram、WhatsApp 五个地方同时找你。没有 Chatwoot,你的客服得开五个 App;有了它,所有对话进同一个收件箱,每条对话像一张工单,有状态(open / pending / resolved)、有负责人、有标签。

再进一步:大部分客户问的是重复问题("怎么退款?""营业时间?")。Captain 就是来接这第一棒的 AI——它读你上传的帮助文档,客户一问,它先自己答;答不了再 handoff(转交)给真人。

它能做什么(Captain 视角):

  • 客户消息进来时自动生成回复(基于你的知识库,不靠模型瞎编)。
  • 把帮助文档/网页自动拆成 FAQ 问答对并做向量索引,供检索。
  • 多个专门 agent(scenario)分工:比如"退款专员""技术支持专员",由一个总 agent 按场景分流。
  • 工具:查 FAQ、加标签、改优先级、写私有备注、调你自己的 HTTP API。
  • 搞不定时转人工,并自动发"稍等,正在转接"之类的话术。
  • 还有 Copilot:不是面向客户,而是坐在人类坐席旁边的副驾,帮坐席查资料、起草回复。

用起来什么样: 你在后台建一个 Assistant(给它名字、产品名、几条"回答准则"和"红线"),上传几篇帮助文档,把它绑到某个收件箱。之后客户在那个收件箱发消息,Captain 就接管了——人类坐席只在转人工后才出现。

一句话直觉: 把 Captain 想成一个只读你公司知识库、有明确转人工开关的前台。它不是无所不知的 ChatGPT,而是被严格约束"只答知识库里有的、答不了就喊人"的客服机器人。

2. 顶层全景(它大概怎么转)

Captain 不是一个独立服务,而是长在 Chatwoot 这个 Rails 单体里的一组 model + service + job,挂在"消息进来"这个事件上。下面这张图是一条客户消息的完整旅程。

怎么读这张图: 从左上的"客户发消息"出发,顺箭头走;菱形是判断点;走到"转人工"或"发出 AI 回复"就结束。

客户发来一条消息(incoming)


┌─────────────────────────────┐
│ HookExecutionService │ 消息后处理钩子(enterprise 覆盖版)
│ trigger_templates │
└─────────────┬───────────────┘
│ 这个收件箱绑了 Captain assistant 吗?
┌──────┴───────┐
否 是
│ │ 额度还够吗(captain_active?)
▼ ┌───┴────┐
走普通流程 够 不够
(人工/自动回复) │ │
▼ ▼
ResponseBuilderJob 立即转人工 perform_handoff
(异步后台任务)

┌────────┴─────────┐
V2 开了? 没开(V1)
│ │
▼ ▼
AgentRunnerService AssistantChatService
(多 agent + handoff) (单 chat + 工具循环)
│ │
└────────┬─────────┘

process_response 分发
┌────────┼─────────┐
handoff 工具 普通回复 出错
被调用了 │ │
│ ▼ ▼
▼ 建 outgoing 消息 转人工
转人工 + 扣一次额度

主要部件一句话职责:

部件干什么在哪
HookExecutionService(EE 覆盖)消息进来后判断要不要叫 Captain,额度不够则直接转人工enterprise/app/services/enterprise/message_templates/hook_execution_service.rb
Captain::Conversation::ResponseBuilderJob后台任务:生成回复并落地成消息,或执行转人工enterprise/app/jobs/captain/conversation/response_builder_job.rb
Captain::Assistant::AgentRunnerService(V2)ai-agents gem 的多 agent runner,带 handoffenterprise/app/services/captain/assistant/agent_runner_service.rb
Captain::Llm::AssistantChatService(V1)单个 RubyLLM chat,挂工具,JSON 输出enterprise/app/services/captain/llm/assistant_chat_service.rb
Captain::Assistant(model)AI 助手的配置:名字、产品名、准则、红线、工具、scenarioenterprise/app/models/captain/assistant.rb
Captain::AssistantResponse(model)一条 FAQ 问答对 + 它的 1536 维向量,用 pgvector 检索enterprise/app/models/captain/assistant_response.rb
Captain::Document(model)一篇被抓取的文档/网页/PDF,会被拆成多条 FAQenterprise/app/models/captain/document.rb
Captain::Tools::*agent 能调的工具(FAQ 检索、转人工、加标签等)enterprise/lib/captain/tools/

主线走一遍(高层):

  1. 客户在绑了 Captain 的收件箱发消息,会话状态是 pending(等机器人处理)。
  2. 消息钩子确认"该收件箱有 assistant 且额度够",异步派 ResponseBuilderJob
  3. Job 按 feature flag 选 V2(多 agent)或 V1(单 chat),把历史消息喂进去生成回复。
  4. agent 内部会调工具:典型是先 faq_lookup 查知识库,把命中的问答塞进上下文再答。
  5. Job 拿到结果分三路:调了 handoff 工具 → 转人工;普通回复 → 建一条 outgoing 消息发给客户、扣一次额度;出错 → 兜底转人工。

3. 阅读地图(建议顺序)

这是个多子系统项目,按下面顺序读最顺:

  1. 01-captain-overview.md — 先把"一条消息进来到 AI 回复"这条主线走通。理解触发、pending 状态、转人工的三岔路。
  2. 02-agent-runner-v2.md — V2 的核心:Chatwoot 怎么把一个 Assistant model 变成 ai-agents gem 的 Agent,怎么用 scenario 做多 agent handoff。
  3. 03-rag-knowledge.md — 知识从哪来:文档抓取 → LLM 拆 FAQ → 向量化 → pgvector 余弦检索。这是"不让模型瞎编"的地基。
  4. 04-tools-and-copilot.md — 工具系统(内置 + 自定义 HTTP)、handoff 工具的双重信号、以及面向人类坐席的 Copilot。
  5. 05-internals-and-edges.md — 巧妙之处、V1↔V2 取舍、边界局限、横向对比、完整代码地图。

关键前提:Captain 的几乎所有代码都在 enterprise/ 目录下(Chatwoot 的企业版叠加层),OSS 主干只放扩展点。读 Captain 就是读 enterprise/app/{models,services,jobs}/captainenterprise/lib/captain