fix(captain/mcp): tool descriptions sem exemplos motel-flavored + brand match

Bug 1: send_suite_images_tool description mencionava 'Master, Luxo, Mini
Chalé 45' como exemplos. Quando outros agentes (ex: Juliana de Qnn01)
faziam tools/list, o LLM via essas categorias e usava como referência —
respondia oferecendo Mini Chalé 45 e Suíte Ouro pra cliente do 1001
Noites Ceilândia (que não tem essas categorias). Removidos exemplos.

Bug 2: lookup_brand fazia fuzzy match permissivo demais. 'Hoteis 1001
Noites' e 'Hotel 1001 Noites Prime' ambos contêm '1001 Noites' — quem
vinha primeiro no .find ganhava (Prime, ID menor). Juliana de Qnn01
(brand 'Hoteis 1001 Noites') saiu do Construtor com SOUL.md dizendo
'Hotel 1001 Noites Prime'. Fix: prioriza brand do parent_unit (fonte
canônica), depois exact-match casecmp, depois fuzzy.

Tudo isso vale pra Construtor entregar agente certo da próxima vez —
foram os 2 erros de provisionamento da Juliana hoje.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rodribm10 2026-05-02 14:07:15 -03:00
parent ed21722dc4
commit f0f8322cce
2 changed files with 17 additions and 10 deletions

View File

@ -130,11 +130,16 @@ class Captain::Mcp::Tools::SaveAgentSpecTool < Captain::Mcp::Tools::BaseTool
def lookup_brand(parent, brand_name) def lookup_brand(parent, brand_name)
return nil if parent.nil? return nil if parent.nil?
if brand_name.present? # Sempre prefere a brand do parent_unit (fonte de verdade — Construtor copiou
hit = Captain::Brand.where(account_id: parent.account_id).find { |b| brand_matches?(b.name, brand_name) } # daquele agente). Spec.brand passado é só hint, pode estar errado/abreviado.
return hit if hit parent_brand = parent.captain_unit&.brand || parent.captain_inboxes.first&.captain_unit&.brand
end return parent_brand if parent_brand
parent.captain_inboxes.first&.captain_unit&.brand
return nil if brand_name.blank?
candidates = Captain::Brand.where(account_id: parent.account_id)
candidates.find { |b| b.name.casecmp?(brand_name) } ||
candidates.find { |b| brand_matches?(b.name, brand_name) }
end end
def parent_unit_for(parent) def parent_unit_for(parent)

View File

@ -5,8 +5,10 @@
# Captain::GalleryItem da inbox atual (com fallback pra acervo global) e # Captain::GalleryItem da inbox atual (com fallback pra acervo global) e
# envia até `limit` imagens como mensagens outgoing na conversa. # envia até `limit` imagens como mensagens outgoing na conversa.
# #
# Search: aceita `suite_category` (ex: "Master", "Luxo") OU `suite_number` # Search: aceita `suite_category` (nome da categoria configurada na unidade)
# (ex: "101"), mutuamente exclusivos. Match case-insensitive, fuzzy. # OU `suite_number` (ex: "101"), mutuamente exclusivos. Match case-insensitive,
# fuzzy. NÃO incluir exemplos de categorias específicas aqui — vaza pro LLM
# em outros agentes via tools/list.
# #
# Pré-requisito: cadastro do Captain::GalleryItem via painel UI do # Pré-requisito: cadastro do Captain::GalleryItem via painel UI do
# Chatwoot — Captain::Mcp não cria fotos, só consome o catálogo. # Chatwoot — Captain::Mcp não cria fotos, só consome o catálogo.
@ -22,8 +24,8 @@ class Captain::Mcp::Tools::SendSuiteImagesTool < Captain::Mcp::Tools::BaseTool
def description def description
'Envia fotos da suíte pra conversa do cliente. Use quando ele pedir foto/imagem ' \ 'Envia fotos da suíte pra conversa do cliente. Use quando ele pedir foto/imagem ' \
'("manda uma foto", "tem como ver?"). Busca no catálogo da inbox atual (fallback ' \ '("manda uma foto", "tem como ver?"). Busca no catálogo da inbox atual (fallback ' \
'global). Passe `suite_category` (ex: "Master", "Luxo", "Mini Chalé 45") OU ' \ 'global). Passe `suite_category` (nome da categoria conforme cadastro da unidade) ' \
'`suite_number` (ex: "101") — não combine os dois.' 'OU `suite_number` (ex: "101") — não combine os dois.'
end end
def input_schema # rubocop:disable Metrics/MethodLength def input_schema # rubocop:disable Metrics/MethodLength
@ -36,7 +38,7 @@ class Captain::Mcp::Tools::SendSuiteImagesTool < Captain::Mcp::Tools::BaseTool
}, },
suite_category: { suite_category: {
type: 'string', type: 'string',
description: 'Nome/tipo da suíte (ex: "Master", "Luxo", "Mini Chalé 45"). Use quando o cliente pede pelo NOME da categoria.' description: 'Nome/tipo da suíte conforme cadastro da unidade. Use quando o cliente pede pelo NOME da categoria.'
}, },
suite_number: { suite_number: {
type: 'string', type: 'string',