数据截至 (上游 commit 25da7dc6f0e8)
工具系统与 Copilot
agent 的"手脚"就是工具。本章讲 Captain 的工具基类、三类典型工具,以及一条和客服机器人平行的链路——给人类坐席用的 Copilot 副驾。
4.1 工具基类:站在 RubyLLM::Tool 上
所有 Captain 工具继承自 gem 的 RubyLLM::Tool,Chatwoot 包了一层基类塞进 assistant/user 上下文:
# enterprise/app/services/captain/tools/base_tool.rb:1
class Captain::Tools::BaseTool < RubyLLM::Tool
prepend Captain::Tools::Instrumentation
attr_accessor :assistant
def initialize(assistant, user: nil)
@assistant = assistant
@user = user
super()
end
end
基类还提供权限检查 user_has_permission——给 Copilot 那些"代表某个坐席操作"的工具用,检查这个 user 在该账号下有没有对应权限(base_tool.rb:18)。
定义一个工具,声明 description + param 即可,gem 会自动把它转成 LLM 的 function schema:
# enterprise/lib/captain/tools/faq_lookup_tool.rb:1
class Captain::Tools::FaqLookupTool < Captain::Tools::BasePublicTool
description 'Search FAQ responses using semantic similarity to find relevant answers'
param :query, type: 'string', desc: 'The question or topic to search for in the FAQ database'
def perform(_tool_context, query:); ...; end
end
4.2 内置工具:assistant 默认拿什么
主 agent 默认带两个工具——查 FAQ 和转人工:
# enterprise/app/models/captain/assistant.rb:94
def agent_tools
[
self.class.resolve_tool_class('faq_lookup').new(self),
self.class.resolve_tool_class('handoff').new(self)
]
end
库里还有更多内置工具(enterprise/lib/captain/tools/):加标签 add_label_to_conversation_tool、改优先级 update_priority_tool、写私有备注 add_private_note_tool、写联系人备注 add_contact_note_tool、解决会话 resolve_conversation_tool、通用 HTTP http_tool。scenario agent 可以按需挑选这些(见第 2 章 scenario 的 resolved_tools)。
4.3 handoff 工具:双重信号的来源
handoff 工具值得单独看,因为它既在 agent 循环内立即生效,又给上层留信号。第 1 章的三岔路就靠它。
# enterprise/lib/captain/tools/handoff_tool.rb:5
def perform(tool_context, reason: nil)
conversation = find_conversation(tool_context.state) # 从 context.state 取会话
return 'Conversation not found' unless conversation
trigger_handoff(conversation, reason)
"Conversation handed off to human support team..."
end
def trigger_handoff(conversation, reason)
conversation.messages.create!(message_type: :outgoing, private: true, # 把转交理由写成私有备注
sender: @assistant, content: reason)
conversation.bot_handoff! # 切 open + 派事件
send_out_of_office_message_if_applicable(conversation) # 必要时发离线语
end
注意它在 agent 循环内就已经 bot_handoff! 了——会话状态当场变 open。所以回到第 1 章 process_response:V2 handoff 触发后若会话已不 pending,说明工具成功执行了,只需补一条客户可见的 follow-up(process_v2_handoff);若还 pending(工具内部出错返回了 "Conversation not found"),才回落到完整的 V1 转人工。这就是那段"V2 优先"逻辑的根因。