数据截至 (上游 commit 25da7dc6f0e8)
巧妙之处、取舍、边界与代码地图
5.1 巧妙之处(可借鉴的技术)
① "只用工具拿到的信息"作为防瞎编硬约束。 prompt 里明令禁止用训练知识,所有事实必须来自 faq_lookup(enterprise/lib/captain/prompts/assistant.liquid:12)。配合 RAG,把一个通用 LLM"驯化"成只答你知识库的客服。这是客服 agent 区别于通用聊天机器人的关键取舍——宁可说"不知道"也不瞎编。
② handoff 同时是"动作"和"信号",且不计费。 工具完成的回调把 captain_v2_handoff_tool_called 写进 run context(runner_instrumentation_helper.rb:49-56、track_handoff_usage :73-82),再由 agent_run_response.rb:17 映射成结构化响应里的 handoff_tool_called 带回上层做后续编排。计费侧 handoff 单走 process_v2_handoff_response,capture_assistant_session 记 credits_consumed: 0.0(正常回复是 1.0,分别见 response_builder_job.rb:121、:105)——商业上很合理:AI 没解决问题不该收钱。
③ 信号镜像到实例变量,扛住 runner 抛错。 即便 runner 在返回前 崩了、context 不可达,镜像到实例变量的 @handoff_tool_called 仍能让 error_response 报告真实状态(镜像写在 runner_instrumentation_helper.rb:76-78,error_response 读它在 agent_run_response.rb:57-64,初始化在 agent_runner_service.rb:22)。这是"分布式状态 + 异常"下的可靠性细节。
④ 状态用白名单切片,不把整个 AR 对象塞进 LLM。 CONVERSATION_STATE_ATTRIBUTES 等常量精确控制喂给模型的字段(runner_state_helper.rb:2),既省 token 又防敏感字段泄露。联系人属性还有 feature_contact_attributes 开关——没开就根本不喂(agentable.rb:26)。
⑤ 重新同步文档不覆盖人工修订。 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:33、custom_tool.rb:38)。这种"被外部约束倒逼的命名工程"很真实。
5.2 V1 与 V2:为什么并存
Captain 有两套生成引擎,用 feature flag captain_integration_v2 切换(response_builder_job.rb:203)。
V1 AssistantChatService | V2 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_object | response_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-agentsgem 的register_handoffs(第 2 章)。和那些自研 planner/router 的项目相比,这是"买现成轮子"的务实路线。 - RAG 路线:用 pgvector + 余弦最近邻 + LLM 拆 FAQ(第 3 章),没上独立向量数据库——因为它本来就跑在 Postgres 上,复用基础设施。
5.5 代码地图(导航索引)
| 主题 | 文件 | 关键符号 |
|---|---|---|
| 消息触发 Captain | enterprise/app/services/enterprise/message_templates/hook_execution_service.rb | should_process_captain_response?、schedule_captain_response、perform_handoff |
| 主任务编排 | enterprise/app/jobs/captain/conversation/response_builder_job.rb | perform、process_response、collect_previous_messages、captain_v2_enabled? |
| V2 agent 引擎 | enterprise/app/services/captain/assistant/agent_runner_service.rb | generate_response、build_and_wire_agents、process_agent_result、track_handoff_usage |
| V1 chat 引擎 | enterprise/app/services/captain/llm/assistant_chat_service.rb | generate_response、build_tools、system_message |
| V1 工具循环 | enterprise/app/helpers/captain/chat_helper.rb | request_chat_completion、setup_event_handlers、with_agent_session |
| model→Agent 翻译 | enterprise/app/models/concerns/agentable.rb | agent、agent_instructions、agent_model |
| AI 助手配置 | enterprise/app/models/captain/assistant.rb | agent_tools、prompt_context、available_agent_tools |
| 场景/多 agent | enterprise/app/models/captain/scenario.rb | handoff_key、agent_tools、resolve_tool_references、MAX_HANDOFF_TOOL_NAME_LENGTH |
| 主 agent prompt | enterprise/lib/captain/prompts/assistant.liquid | (Liquid 模板:身份/核心规则/决策框架/handoff 协议) |
| prompt 渲染 | enterprise/lib/captain/prompt_renderer.rb | render、snippet_file_system |
| 结构化输出 schema | enterprise/lib/captain/response_schema.rb | Captain::ResponseSchema |
| FAQ 检索工具 | enterprise/lib/captain/tools/faq_lookup_tool.rb | perform、format_response、should_show_source? |
| 转人工工具 | enterprise/lib/captain/tools/handoff_tool.rb | perform、trigger_handoff |
| 工具基类 | enterprise/app/services/captain/tools/base_tool.rb | Captain::Tools::BaseTool、user_has_permission |
| 自定义 HTTP 工具 | enterprise/app/models/captain/custom_tool.rb | to_tool_metadata、generate_slug、ensure_within_limit、MAX_PER_ACCOUNT |
| FAQ 数据 模型+检索 | enterprise/app/models/captain/assistant_response.rb | search、has_neighbors、update_response_embedding |
| 文档模型 | enterprise/app/models/captain/document.rb | enqueue_crawl_job、enqueue_response_builder_job、pdf_document? |
| 文档→FAQ 任务 | enterprise/app/jobs/captain/documents/response_builder_job.rb | generate_faqs、reset_previous_responses、create_response |
| FAQ 生成(LLM) | enterprise/app/services/captain/llm/faq_generator_service.rb | generate、parse_response |
| Copilot(坐席副驾) | enterprise/app/services/captain/copilot/chat_service.rb | generate_response、build_tools、build_messages |
| 收件箱 Captain 开关 | enterprise/app/models/enterprise/inbox.rb | active_bot?、captain_active?、more_responses? |
| 会话状态/转人工 | app/models/conversation.rb | enum status、bot_handoff! |
| LLM 服务基类 | enterprise/app/services/llm/base_ai_service.rb | chat、setup_model、sanitize_json_response |
| 计费/额度任务基类 | enterprise/lib/enterprise/captain/base_task_service.rb | perform、responses_available?、increment_usage |
通读完你应能讲清: Chatwoot 把客户对话建模成有状态的工单;Captain 在会话 pending 时接管,用 RAG(文档→FAQ→pgvector 检索)拿事实、用多 agent handoff(ai-agents gem)分流、用工具执 行动作,搞不定就干净地 bot_handoff! 转人工,且转人工不计费。V1/V2 两套引擎并存是为灰度迁移。