From 90d4fcf95deb1afd5798d257d5d83c0587836c40 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 3 Oct 2025 19:09:09 +0530 Subject: [PATCH] fix: tool class name --- enterprise/app/models/concerns/toolable.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/enterprise/app/models/concerns/toolable.rb b/enterprise/app/models/concerns/toolable.rb index 24c16c6f4..bae1771e4 100644 --- a/enterprise/app/models/concerns/toolable.rb +++ b/enterprise/app/models/concerns/toolable.rb @@ -3,7 +3,10 @@ module Concerns::Toolable def tool(assistant) custom_tool_record = self + # Convert slug to valid Ruby constant name (replace hyphens with underscores, then camelize) + class_name = custom_tool_record.slug.underscore.camelize + # Always create a fresh class to reflect current metadata tool_class = Class.new(Captain::Tools::HttpTool) do description custom_tool_record.description @@ -15,6 +18,16 @@ module Concerns::Toolable end end + # Register the dynamically created class as a constant in the Captain::Tools namespace. + # This is required because RubyLLM's Tool base class derives the tool name from the class name + # (via Class#name). Anonymous classes created with Class.new have no name and return empty strings, + # which causes "Invalid 'tools[].function.name': empty string" errors from the LLM API. + # By setting it as a constant, the class gets a proper name (e.g., "Captain::Tools::CatFactLookup") + # which RubyLLM extracts and normalizes to "cat-fact-lookup" for the LLM API. + # We refresh the constant on each call to ensure tool metadata changes are reflected. + Captain::Tools.send(:remove_const, class_name) if Captain::Tools.const_defined?(class_name, false) + Captain::Tools.const_set(class_name, tool_class) + tool_class.new(assistant, self) end