Class: Insika::ToolCatalog
- Inherits:
-
Object
- Object
- Insika::ToolCatalog
- Defined in:
- lib/insika/tool_catalog.rb
Overview
"Level 1" (progressive disclosure) view of the tools: just
name+description, the analog of SkillCatalog for tools. It does NOT read
disk (the tools are already in the ToolRegistry, registered at boot) and does NOT
inherit from RubyLLM::Tool — duck typing over .description (pure, testable without
the gem).
The canonical description does not live in the Entry (Registry::Entry has no such
field): it comes from the tool INSTANCE (factory.call.description). That is why the
catalog is LAZY: it only instantiates the tools on the first query (all), not at
boot. A deployment without tools_deferred never touches the catalog and pays for
no instantiation at all; a broken factory surfaces on first use (where the
Executor would also catch it at stage 3), not at construction.
Defined Under Namespace
Classes: Entry
Instance Method Summary collapse
- #all ⇒ Object
-
#format_for_prompt(entries = all) ⇒ Object
Level 1 injected into the prompt — mirror of SkillCatalog#format_for_prompt, swapping the tag and the final instruction (load_skill -> tool_search).
-
#initialize(tool_registry:) ⇒ ToolCatalog
constructor
A new instance of ToolCatalog.
-
#reload ⇒ Object
Reloads the index (after authoring a data-tool in the overlay).
-
#search(query, within: nil) ⇒ Object
PURE matcher: case-insensitive, substring/keyword, NO embeddings.
-
#subset(names) ⇒ Object
Allowed deferred slice (typically allowed_tools ∩ tools_deferred).
Constructor Details
#initialize(tool_registry:) ⇒ ToolCatalog
Returns a new instance of ToolCatalog.
19 20 21 |
# File 'lib/insika/tool_catalog.rb', line 19 def initialize(tool_registry:) @tool_registry = tool_registry end |
Instance Method Details
#all ⇒ Object
23 24 25 |
# File 'lib/insika/tool_catalog.rb', line 23 def all @entries ||= build_entries end |
#format_for_prompt(entries = all) ⇒ Object
Level 1 injected into the prompt — mirror of SkillCatalog#format_for_prompt, swapping the tag and the final instruction (load_skill -> tool_search).
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/insika/tool_catalog.rb', line 60 def format_for_prompt(entries = all) return "" if entries.empty? lines = entries.map { |e| %( <tool name="#{e.name}">#{e.description}</tool>) }.join("\n") <<~PROMPT.strip <available_tools> #{lines} </available_tools> Before using a tool above, call `tool_search` with what you need to do to enable it in this conversation. PROMPT end |
#reload ⇒ Object
Reloads the index (after authoring a data-tool in the overlay). Mirrors
SkillCatalog#reload — level-1/tool_search starts seeing the new tool without a
restart. An in-flight turn has already captured all.
30 31 32 33 |
# File 'lib/insika/tool_catalog.rb', line 30 def reload @entries = build_entries self end |
#search(query, within: nil) ⇒ Object
PURE matcher: case-insensitive, substring/keyword, NO
embeddings. name weighs 2, description weighs 1; ties broken by original index
(Ruby's sort_by is not stable). within: restricts the universe via subset.
46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/insika/tool_catalog.rb', line 46 def search(query, within: nil) terms = query.to_s.downcase.split(/\s+/).reject(&:empty?) return [] if terms.empty? universe = within ? subset(within) : all scored = universe.each_with_index.filter_map do |entry, idx| score = score_entry(entry, terms) [entry, score, idx] if score.positive? end scored.sort_by { |_entry, score, idx| [-score, idx] }.map(&:first) end |
#subset(names) ⇒ Object
Allowed deferred slice (typically allowed_tools ∩ tools_deferred). Names outside the catalog are silently ignored (safe failure: less exposure, never more).
38 39 40 41 |
# File 'lib/insika/tool_catalog.rb', line 38 def subset(names) wanted = Array(names).map(&:to_s) all.select { |e| wanted.include?(e.name) } end |