跳到主要内容

巧妙之处、取舍、边界与代码地图

5.1 巧妙之处(可借鉴的技术)

① "只用工具拿到的信息"作为防瞎编硬约束。 prompt 里明令禁止用训练知识,所有事实必须来自 faq_lookup(enterprise/lib/captain/prompts/assistant.liquid:12)。配合 RAG,把一个通用 LLM"驯化"成只答你知识库的客服。这是客服 agent 区别于通用聊天机器人的关键取舍——宁可说"不知道"也不瞎编

② handoff 同时是"动作"和"信号",且不计费。 handoff 工具在 agent 循环内当场 bot_handoff!,又通过 handoff_tool_called 把信号带回上层做后续编排(agent_runner_service.rb:196)。而计费明确跳过 handoff(:206)——商业上很合理:AI 没解决问题不该收钱。

③ 信号镜像到实例变量,扛住 runner 抛错。 即便 runner 在返回前崩了、context 不可达,@handoff_tool_called 仍能让 error_response 报告真实状态(agent_runner_service.rb:202)。这是"分布式状态 + 异常"下的可靠性细节。

④ 状态用白名单切片,不把整个 AR 对象塞进 LLM。 CONVERSATION_STATE_ATTRIBUTES 等常量精确控制喂给模型的字段(agent_runner_service.rb:9),既省 token 又防敏感字段泄露。联系人属性还有 feature_contact_attributes 开关——没开就根本不喂(agentable.rb:24)。

⑤ 重新同步文档不覆盖人工修订。 reset_previous_responses 只删 edited: false 的 FAQ(documents/response_builder_job.rb:64),保住坐席手改过的答案。

⑥ Captain 接管时抑制其他自动消息。 欢迎语/离线语/邮箱采集在 Captain 工作期间全部 return false(hook_execution_service.rb:12-28),避免客户同时收到机器人答复和"我们已离线"的矛盾消息。

⑦ 函数名长度预算管理。 scenario 和 custom_tool 都精确计算 agent/工具名长度,因为 OpenAI 函数名 ≤64 字符、gem 还要加前缀(scenario.rb:33custom_tool.rb:38)。这种"被外部约束倒逼的命名工程"很真实。

5.2 V1 与 V2:为什么并存

Captain 有两套生成引擎,用 feature flag captain_integration_v2 切换(response_builder_job.rb:203)。

V1 AssistantChatServiceV2 AgentRunnerService
引擎单个 RubyLLM.chat + 工具回调循环ai-agents gem 的 Agents::Runner
agent 数一个主 agent + N 个 scenario,可 handoff
工具循环自己挂 on_tool_call 回调(chat_helper.rb:47)gem 内部跑(最多 100 轮)
输出response_format: json_objectresponse_schema(结构化)
转人工信号字面量 'conversation_handoff' 或 V1 分类器handoff_tool_called 布尔
额外机制V1 action classifier、false-promise 修复原生多 agent

V1 还保留了两个"后处理修补"步骤,V2 不需要(因为多 agent 编排更自然):

  • action classifier:再过一遍 LLM 判断这次回复该不该转人工(v1_action_classifier.rb:8)。
  • false promise 修复:检测并修正"机器人许下做不到的承诺"(response_builder_job.rb:42)。

取舍解读: V1 是"手搓 agent 循环 + 一堆补丁";V2 把循环和 handoff 外包给成熟 gem,逻辑更干净,补丁更少。并存是为了灰度迁移——老账号 V1,新账号 V2。

5.3 边界与局限(诚实)

  • Captain 是企业版(EE)功能。 几乎所有代码在 enterprise/ 下,OSS 主干只放扩展点(如 Enterprise::Inbox#active_bot? 覆盖 inbox.active_bot?)。纯 OSS 部署没有 Captain。
  • 只懂知识库,刻意不通用。 prompt 禁用训练知识(assistant.liquid:12),所以你没上传的内容它一律答不了或转人工。这是设计选择,不是 bug。
  • 强依赖外部 gem 和 OpenAI。 agent 引擎是 ai-agents(Gemfile:198),LLM 抽象是 ruby_llm,embedding 走 OpenAI;向量库依赖 Postgres 的 pgvector 扩展。这些不在本仓库源码内,本文档对 Agents::Runner.run 的内部轮转描述属 (inferred)。
  • 有额度/计费闸。 云版按 usage_limits[:captain][:responses] 限流,额度用完直接转人工(enterprise/inbox.rb:21);文档数也有上限(document.rb:153)。
  • prompt 注入面真实存在。 客户消息和检索到的 FAQ 都进 prompt;虽有"不许越界"的 guardrail,但本质仍是文本约束,不是硬隔离。

5.4 横向对比(同 shelf 兄弟)

Captain 在 chat-agents 这一格里是"产品化的客服 agent",和偏框架/偏通用的兄弟项目取舍不同:

  • vs 通用 agent 框架:Captain 不追求"什么都能干",而是把 agent 死死焊在客服工单流程上——状态机(pending/open)、转人工、计费、知识库审核,全是客服业务的具体化。
  • 多 agent 实现:它没自研 orchestration,而是用 ai-agents gem 的 register_handoffs(第 2 章)。和那些自研 planner/router 的项目相比,这是"买现成轮子"的务实路线。
  • RAG 路线:用 pgvector + 余弦最近邻 + LLM 拆 FAQ(第 3 章),没上独立向量数据库——因为它本来就跑在 Postgres 上,复用基础设施。

5.5 代码地图(导航索引)

主题文件关键符号
消息触发 Captainenterprise/app/services/enterprise/message_templates/hook_execution_service.rbshould_process_captain_response?schedule_captain_responseperform_handoff
主任务编排enterprise/app/jobs/captain/conversation/response_builder_job.rbperformprocess_responsecollect_previous_messagescaptain_v2_enabled?
V2 agent 引擎enterprise/app/services/captain/assistant/agent_runner_service.rbgenerate_responsebuild_and_wire_agentsprocess_agent_resulttrack_handoff_usage
V1 chat 引擎enterprise/app/services/captain/llm/assistant_chat_service.rbgenerate_responsebuild_toolssystem_message
V1 工具循环enterprise/app/helpers/captain/chat_helper.rbrequest_chat_completionsetup_event_handlerswith_agent_session
model→Agent 翻译enterprise/app/models/concerns/agentable.rbagentagent_instructionsagent_model
AI 助手配置enterprise/app/models/captain/assistant.rbagent_toolsprompt_contextavailable_agent_tools
场景/多 agententerprise/app/models/captain/scenario.rbhandoff_keyagent_toolsresolve_tool_referencesMAX_HANDOFF_TOOL_NAME_LENGTH
主 agent promptenterprise/lib/captain/prompts/assistant.liquid(Liquid 模板:身份/核心规则/决策框架/handoff 协议)
prompt 渲染enterprise/lib/captain/prompt_renderer.rbrendersnippet_file_system
结构化输出 schemaenterprise/lib/captain/response_schema.rbCaptain::ResponseSchema
FAQ 检索工具enterprise/lib/captain/tools/faq_lookup_tool.rbperformformat_responseshould_show_source?
转人工工具enterprise/lib/captain/tools/handoff_tool.rbperformtrigger_handoff
工具基类enterprise/app/services/captain/tools/base_tool.rbCaptain::Tools::BaseTooluser_has_permission
自定义 HTTP 工具enterprise/app/models/captain/custom_tool.rbto_tool_metadatagenerate_slugensure_within_limitMAX_PER_ACCOUNT
FAQ 数据模型+检索enterprise/app/models/captain/assistant_response.rbsearchhas_neighborsupdate_response_embedding
文档模型enterprise/app/models/captain/document.rbenqueue_crawl_jobenqueue_response_builder_jobpdf_document?
文档→FAQ 任务enterprise/app/jobs/captain/documents/response_builder_job.rbgenerate_faqsreset_previous_responsescreate_response
FAQ 生成(LLM)enterprise/app/services/captain/llm/faq_generator_service.rbgenerateparse_response
Copilot(坐席副驾)enterprise/app/services/captain/copilot/chat_service.rbgenerate_responsebuild_toolsbuild_messages
收件箱 Captain 开关enterprise/app/models/enterprise/inbox.rbactive_bot?captain_active?more_responses?
会话状态/转人工app/models/conversation.rbenum statusbot_handoff!
LLM 服务基类enterprise/app/services/llm/base_ai_service.rbchatsetup_modelsanitize_json_response
计费/额度任务基类enterprise/lib/enterprise/captain/base_task_service.rbperformresponses_available?increment_usage

通读完你应能讲清: Chatwoot 把客户对话建模成有状态的工单;Captain 在会话 pending 时接管,用 RAG(文档→FAQ→pgvector 检索)拿事实、用多 agent handoff(ai-agents gem)分流、用工具执行动作,搞不定就干净地 bot_handoff! 转人工,且转人工不计费。V1/V2 两套引擎并存是为灰度迁移。