insika 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +361 -0
- data/LICENSE +21 -0
- data/README.md +136 -2
- data/bin/insika +366 -0
- data/docs/AGENTS.md +618 -0
- data/docs/ARCHITECTURE.md +333 -0
- data/docs/BENCHMARK.md +114 -0
- data/docs/CHANNELS.md +453 -0
- data/docs/CONTEXT.md +117 -0
- data/docs/DEPLOY.md +354 -0
- data/docs/EMBEDDING.md +198 -0
- data/docs/EVALS.md +273 -0
- data/docs/LOADTEST.md +232 -0
- data/docs/OBSERVABILITY.md +374 -0
- data/docs/PLUGINS.md +211 -0
- data/docs/REFINEMENT.md +477 -0
- data/docs/RELEASING.md +70 -0
- data/docs/RUNNING-LOCAL.md +153 -0
- data/docs/SANDBOX.md +114 -0
- data/docs/SECURITY.md +375 -0
- data/docs/SKILLS.md +284 -0
- data/docs/TOOLS.md +302 -0
- data/docs/WHY.md +137 -0
- data/docs/WORKFLOWS.md +225 -0
- data/docs/build.md +14 -0
- data/docs/index.md +68 -0
- data/docs/onboarding/start.md +126 -0
- data/docs/operate.md +12 -0
- data/docs/ship.md +10 -0
- data/docs/understand.md +10 -0
- data/lib/insika/agent_file_store.rb +125 -0
- data/lib/insika/agent_profile.rb +255 -0
- data/lib/insika/alert_dispatcher.rb +139 -0
- data/lib/insika/allowlist.rb +28 -0
- data/lib/insika/baseline_store.rb +74 -0
- data/lib/insika/budget_ledger.rb +135 -0
- data/lib/insika/capability/resolved_tool.rb +34 -0
- data/lib/insika/capability_registry.rb +112 -0
- data/lib/insika/channel_delivery.rb +153 -0
- data/lib/insika/channel_registry.rb +30 -0
- data/lib/insika/channels/relay.rb +178 -0
- data/lib/insika/channels/web/widget.js +283 -0
- data/lib/insika/channels/web.rb +211 -0
- data/lib/insika/channels/webhook.rb +58 -0
- data/lib/insika/chat_builder.rb +303 -0
- data/lib/insika/checkpoint.rb +13 -0
- data/lib/insika/checkpoint_store.rb +153 -0
- data/lib/insika/circuit_state.rb +114 -0
- data/lib/insika/coercion.rb +58 -0
- data/lib/insika/command.rb +32 -0
- data/lib/insika/command_bus.rb +39 -0
- data/lib/insika/commands/agent_payload.rb +43 -0
- data/lib/insika/commands/approve_action.rb +46 -0
- data/lib/insika/commands/cancel_task.rb +33 -0
- data/lib/insika/commands/create_agent.rb +54 -0
- data/lib/insika/commands/create_session.rb +67 -0
- data/lib/insika/commands/delete_agent.rb +33 -0
- data/lib/insika/commands/delete_agent_file.rb +50 -0
- data/lib/insika/commands/delete_data_tool.rb +33 -0
- data/lib/insika/commands/delete_llm_provider.rb +36 -0
- data/lib/insika/commands/delete_mcp.rb +30 -0
- data/lib/insika/commands/delete_skill.rb +43 -0
- data/lib/insika/commands/delete_system_file.rb +29 -0
- data/lib/insika/commands/gate_refinement.rb +245 -0
- data/lib/insika/commands/import_mcp_tools.rb +48 -0
- data/lib/insika/commands/import_tools.rb +81 -0
- data/lib/insika/commands/issue_tenant_token.rb +41 -0
- data/lib/insika/commands/memory_add_note.rb +32 -0
- data/lib/insika/commands/memory_forget_fact.rb +32 -0
- data/lib/insika/commands/memory_put_fact.rb +35 -0
- data/lib/insika/commands/pause_task.rb +29 -0
- data/lib/insika/commands/resolve_refinement.rb +126 -0
- data/lib/insika/commands/restore_agent_file.rb +36 -0
- data/lib/insika/commands/restore_data_tool.rb +34 -0
- data/lib/insika/commands/restore_system_file.rb +31 -0
- data/lib/insika/commands/resume_task.rb +85 -0
- data/lib/insika/commands/revoke_token.rb +39 -0
- data/lib/insika/commands/rotate_tenant_token.rb +43 -0
- data/lib/insika/commands/run_refinement.rb +133 -0
- data/lib/insika/commands/send_message.rb +150 -0
- data/lib/insika/commands/set_agent_tools.rb +39 -0
- data/lib/insika/commands/set_skill_agents.rb +112 -0
- data/lib/insika/commands/trigger_workflow.rb +80 -0
- data/lib/insika/commands/update_agent.rb +49 -0
- data/lib/insika/commands/update_settings.rb +33 -0
- data/lib/insika/commands/upsert_llm_provider.rb +34 -0
- data/lib/insika/commands/upsert_mcp.rb +32 -0
- data/lib/insika/commands/write_agent_file.rb +57 -0
- data/lib/insika/commands/write_data_tool.rb +43 -0
- data/lib/insika/commands/write_golden.rb +58 -0
- data/lib/insika/commands/write_skill.rb +60 -0
- data/lib/insika/commands/write_system_file.rb +31 -0
- data/lib/insika/config_store.rb +89 -0
- data/lib/insika/context/builder.rb +166 -0
- data/lib/insika/context/catalog_provider.rb +23 -0
- data/lib/insika/context/fragment.rb +43 -0
- data/lib/insika/context/priority.rb +30 -0
- data/lib/insika/context/provider.rb +19 -0
- data/lib/insika/context/providers/memory.rb +60 -0
- data/lib/insika/context/providers/prompt.rb +105 -0
- data/lib/insika/context/providers/request.rb +32 -0
- data/lib/insika/context/providers/session.rb +123 -0
- data/lib/insika/context/providers/skill.rb +24 -0
- data/lib/insika/context/providers/skill_trigger.rb +128 -0
- data/lib/insika/context/providers/tool_search.rb +20 -0
- data/lib/insika/context_trace_store.rb +92 -0
- data/lib/insika/delegation_store.rb +153 -0
- data/lib/insika/doctor.rb +539 -0
- data/lib/insika/dsl/definition.rb +55 -0
- data/lib/insika/dsl/runtime.rb +382 -0
- data/lib/insika/dsl/server_boot.rb +98 -0
- data/lib/insika/dsl/system.rb +93 -0
- data/lib/insika/dsl/workflow_adapter.rb +59 -0
- data/lib/insika/dsl.rb +364 -0
- data/lib/insika/edge_limiter.rb +268 -0
- data/lib/insika/egress_guard.rb +75 -0
- data/lib/insika/env_schema.rb +249 -0
- data/lib/insika/errors.rb +201 -0
- data/lib/insika/evals/assertions.rb +247 -0
- data/lib/insika/evals/baseline.rb +69 -0
- data/lib/insika/evals/golden.rb +172 -0
- data/lib/insika/evals/judge.rb +225 -0
- data/lib/insika/evals/pairwise.rb +178 -0
- data/lib/insika/evals/report.rb +115 -0
- data/lib/insika/evals/runner.rb +141 -0
- data/lib/insika/evals/transport.rb +178 -0
- data/lib/insika/event.rb +18 -0
- data/lib/insika/event_stream.rb +132 -0
- data/lib/insika/executor.rb +1995 -0
- data/lib/insika/frontmatter.rb +42 -0
- data/lib/insika/golden_store.rb +145 -0
- data/lib/insika/hooks.rb +48 -0
- data/lib/insika/http_client.rb +63 -0
- data/lib/insika/inbound_log.rb +84 -0
- data/lib/insika/llm_configurator.rb +99 -0
- data/lib/insika/llm_provider_store.rb +83 -0
- data/lib/insika/loop_detector.rb +143 -0
- data/lib/insika/mcp_http_client.rb +67 -0
- data/lib/insika/mcp_store.rb +115 -0
- data/lib/insika/mcp_tool_ingestor.rb +143 -0
- data/lib/insika/memory_store.rb +93 -0
- data/lib/insika/message_origin.rb +76 -0
- data/lib/insika/middleware.rb +36 -0
- data/lib/insika/model_policy.rb +52 -0
- data/lib/insika/model_resolver.rb +176 -0
- data/lib/insika/model_selection.rb +115 -0
- data/lib/insika/onboarding.rb +208 -0
- data/lib/insika/outbox_store.rb +166 -0
- data/lib/insika/overlay_tool_registry.rb +102 -0
- data/lib/insika/pack.rb +102 -0
- data/lib/insika/pack_importer.rb +123 -0
- data/lib/insika/pending_action_store.rb +120 -0
- data/lib/insika/plugin/loader.rb +356 -0
- data/lib/insika/plugin.rb +35 -0
- data/lib/insika/policy/engine.rb +83 -0
- data/lib/insika/policy/policy.rb +120 -0
- data/lib/insika/policy_registry.rb +23 -0
- data/lib/insika/profile_source.rb +143 -0
- data/lib/insika/prompt_catalog.rb +61 -0
- data/lib/insika/provider_error_classifier.rb +160 -0
- data/lib/insika/queue_policy.rb +167 -0
- data/lib/insika/recovery.rb +168 -0
- data/lib/insika/refinement/candidate.rb +159 -0
- data/lib/insika/refinement/evidence_collector.rb +371 -0
- data/lib/insika/refinement/gate.rb +234 -0
- data/lib/insika/refinement/panel.rb +222 -0
- data/lib/insika/refinement/proposer.rb +262 -0
- data/lib/insika/refinement_store.rb +295 -0
- data/lib/insika/registry.rb +59 -0
- data/lib/insika/reliability.rb +185 -0
- data/lib/insika/safety/config.rb +109 -0
- data/lib/insika/safety/detectors.rb +176 -0
- data/lib/insika/safety/factory.rb +102 -0
- data/lib/insika/safety/input_guardrail.rb +102 -0
- data/lib/insika/safety/moderator.rb +94 -0
- data/lib/insika/safety/output_filter.rb +79 -0
- data/lib/insika/safety/output_validator.rb +101 -0
- data/lib/insika/safety/safe_responses.rb +47 -0
- data/lib/insika/sandbox/boundary.rb +93 -0
- data/lib/insika/sandbox/docker.rb +74 -0
- data/lib/insika/sandbox/local.rb +33 -0
- data/lib/insika/sandbox/runner.rb +80 -0
- data/lib/insika/sandbox.rb +85 -0
- data/lib/insika/schema_guard.rb +147 -0
- data/lib/insika/secret_masking.rb +34 -0
- data/lib/insika/server/a2a/agent_card.rb +27 -0
- data/lib/insika/server/a2a/app.rb +112 -0
- data/lib/insika/server/a2a/client.rb +101 -0
- data/lib/insika/server/a2a/errors.rb +32 -0
- data/lib/insika/server/a2a/http.rb +42 -0
- data/lib/insika/server/a2a/message.rb +27 -0
- data/lib/insika/server/a2a/protocol.rb +45 -0
- data/lib/insika/server/a2a/remotes.rb +25 -0
- data/lib/insika/server/a2a/task_projection.rb +40 -0
- data/lib/insika/server/app.rb +1022 -0
- data/lib/insika/server/boot.rb +119 -0
- data/lib/insika/server/rack_app.rb +118 -0
- data/lib/insika/server/responses.rb +165 -0
- data/lib/insika/server/sse_body.rb +96 -0
- data/lib/insika/server/tenant_auth.rb +61 -0
- data/lib/insika/session_actor.rb +162 -0
- data/lib/insika/session_store.rb +143 -0
- data/lib/insika/settings_store.rb +154 -0
- data/lib/insika/shutdown.rb +125 -0
- data/lib/insika/skill_catalog.rb +220 -0
- data/lib/insika/skill_store.rb +127 -0
- data/lib/insika/steer_injector.rb +110 -0
- data/lib/insika/store.rb +52 -0
- data/lib/insika/stores/memory.rb +123 -0
- data/lib/insika/stores/sqlite.rb +183 -0
- data/lib/insika/studio/app.rb +1693 -0
- data/lib/insika/studio/assets/dist/application.css +1 -0
- data/lib/insika/studio/assets/dist/application.js +70 -0
- data/lib/insika/studio/forms.rb +335 -0
- data/lib/insika/studio/nav_icons.rb +31 -0
- data/lib/insika/studio/views/_message.erb +44 -0
- data/lib/insika/studio/views/agent_detail.erb +285 -0
- data/lib/insika/studio/views/agents.erb +63 -0
- data/lib/insika/studio/views/approvals.erb +41 -0
- data/lib/insika/studio/views/chats.erb +34 -0
- data/lib/insika/studio/views/evals.erb +83 -0
- data/lib/insika/studio/views/home.erb +72 -0
- data/lib/insika/studio/views/layout.erb +94 -0
- data/lib/insika/studio/views/login.erb +17 -0
- data/lib/insika/studio/views/mcp.erb +91 -0
- data/lib/insika/studio/views/not_found.erb +5 -0
- data/lib/insika/studio/views/playground.erb +47 -0
- data/lib/insika/studio/views/refinement.erb +234 -0
- data/lib/insika/studio/views/session.erb +137 -0
- data/lib/insika/studio/views/settings.erb +168 -0
- data/lib/insika/studio/views/skills.erb +141 -0
- data/lib/insika/studio/views/system_files.erb +65 -0
- data/lib/insika/studio/views/task.erb +105 -0
- data/lib/insika/studio/views/tasks.erb +33 -0
- data/lib/insika/studio/views/tool_edit.erb +107 -0
- data/lib/insika/studio/views/tools.erb +89 -0
- data/lib/insika/subagent_graph.rb +96 -0
- data/lib/insika/system_file_store.rb +96 -0
- data/lib/insika/task_actor.rb +128 -0
- data/lib/insika/task_store.rb +250 -0
- data/lib/insika/telemetry/pricing.rb +104 -0
- data/lib/insika/telemetry/recorder.rb +228 -0
- data/lib/insika/telemetry.rb +127 -0
- data/lib/insika/testing/store_contract.rb +270 -0
- data/lib/insika/tick.rb +122 -0
- data/lib/insika/token_estimator.rb +16 -0
- data/lib/insika/token_store.rb +168 -0
- data/lib/insika/tool_assembly.rb +140 -0
- data/lib/insika/tool_catalog.rb +89 -0
- data/lib/insika/tool_definition.rb +518 -0
- data/lib/insika/tool_envelope.rb +140 -0
- data/lib/insika/tool_manifest.rb +218 -0
- data/lib/insika/tool_output_compressor.rb +100 -0
- data/lib/insika/tool_registry.rb +21 -0
- data/lib/insika/tool_store.rb +135 -0
- data/lib/insika/tool_trace_store.rb +92 -0
- data/lib/insika/tools/a2a_remote.rb +48 -0
- data/lib/insika/tools/agent_enum.rb +68 -0
- data/lib/insika/tools/concurrency.rb +54 -0
- data/lib/insika/tools/data_defined_tool.rb +219 -0
- data/lib/insika/tools/load_skill.rb +99 -0
- data/lib/insika/tools/remember.rb +53 -0
- data/lib/insika/tools/stuck_signal.rb +44 -0
- data/lib/insika/tools/subagent.rb +75 -0
- data/lib/insika/tools/subagents.rb +77 -0
- data/lib/insika/tools/tool_search.rb +94 -0
- data/lib/insika/turn_output.rb +139 -0
- data/lib/insika/turn_state.rb +162 -0
- data/lib/insika/turn_timing.rb +56 -0
- data/lib/insika/usage_ledger.rb +47 -0
- data/lib/insika/version.rb +3 -1
- data/lib/insika/wiring/graph.rb +249 -0
- data/lib/insika/workflow.rb +185 -0
- data/lib/insika/workflow_registry.rb +33 -0
- data/lib/insika.rb +220 -4
- metadata +412 -8
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
require "async/semaphore"
|
|
5
|
+
|
|
6
|
+
module Insika
|
|
7
|
+
# Turn-scoped assembly of the agent's tool instances (pipeline stage 3 tail):
|
|
8
|
+
# capability resolution, instantiation (Entry#factory | ready instance),
|
|
9
|
+
# turn-context injection, the capability<->direct dedup join, and the
|
|
10
|
+
# ToolEnvelope wrap (stage 7 seam: per-call timeout + side-effect recording).
|
|
11
|
+
#
|
|
12
|
+
# Extracted from the Executor to keep the hot-path file smaller. It is
|
|
13
|
+
# a pure collaborator — it holds only the injected registries/stores and no
|
|
14
|
+
# per-turn state; everything turn-specific arrives via `state`/`turn_context`.
|
|
15
|
+
# The Executor keeps thin delegators (resolve_capabilities/assemble_tool_instances/
|
|
16
|
+
# wrap_tools) so the existing private-method contract stays intact.
|
|
17
|
+
class ToolAssembly
|
|
18
|
+
def initialize(tool_registry:, capability_registry:, event_stream:,
|
|
19
|
+
checkpoint_store:, tool_trace_store:)
|
|
20
|
+
@tool_registry = tool_registry
|
|
21
|
+
@capability_registry = capability_registry
|
|
22
|
+
@event_stream = event_stream
|
|
23
|
+
@checkpoint_store = checkpoint_store
|
|
24
|
+
@tool_trace_store = tool_trace_store
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Resolution sub-step BETWEEN Context and Policy —
|
|
28
|
+
# it does NOT feed candidate_tools (those stay ONLY tool_registry.entries,
|
|
29
|
+
# a capability does not go through the ToolAllowlist). Resolves each
|
|
30
|
+
# capability of the profile to the concrete Entry already registered in the
|
|
31
|
+
# tool_registry and keeps the impl_name -> capability_name mapping for the
|
|
32
|
+
# post-Policy join. Errors
|
|
33
|
+
# (Unavailable/Ambiguous, or an unregistered impl) propagate as a
|
|
34
|
+
# CapabilityError -> single capture in `execute` (stage :capability). Without
|
|
35
|
+
# @capability_registry OR without profile.capabilities: {} (parity).
|
|
36
|
+
def resolve_capabilities(profile, context)
|
|
37
|
+
return {} if @capability_registry.nil?
|
|
38
|
+
|
|
39
|
+
Array(profile.capabilities).each_with_object({}) do |cap_name, names|
|
|
40
|
+
provider = @capability_registry.resolve(cap_name, profile: profile, context: context,
|
|
41
|
+
event_stream: @event_stream)
|
|
42
|
+
next if provider.kind == :workflow # exposure to the agent loop is a follow-up
|
|
43
|
+
|
|
44
|
+
entry = @tool_registry.entries.find { |e| e.name == provider.impl_name.to_s }
|
|
45
|
+
if entry.nil?
|
|
46
|
+
raise CapabilityError, "capability '#{cap_name}' resolveu para impl " \
|
|
47
|
+
"'#{provider.impl_name}', not registered in tool_registry"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
names[entry.name] ||= cap_name.to_s # the 1st capability to claim an impl wins
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Joins the direct instances (Policy/ToolAllowlist) with the
|
|
55
|
+
# capability-sourced ones (grant = profile.capabilities — they never went
|
|
56
|
+
# through Policy). Avoids double-exposure: if the SAME impl_name was also
|
|
57
|
+
# allowed directly, the DIRECT instance is discarded — the model sees only the
|
|
58
|
+
# capability alias.
|
|
59
|
+
def assemble_tool_instances(allowed, state)
|
|
60
|
+
names = state.respond_to?(:capability_names) ? (state.capability_names || {}) : {}
|
|
61
|
+
ctx = state.respond_to?(:turn_context) ? state.turn_context : nil
|
|
62
|
+
return instantiate_tools(allowed, ctx) if names.empty?
|
|
63
|
+
|
|
64
|
+
# Dedup by the ENTRY NAME (registry key = impl_name) BEFORE
|
|
65
|
+
# instantiating — the INSTANCE's `.name` (RubyLLM) is not the registration
|
|
66
|
+
# name.
|
|
67
|
+
direct = Array(allowed).reject { |e| e.respond_to?(:name) && names.key?(e.name.to_s) }
|
|
68
|
+
instantiate_tools(direct, ctx) + capability_tool_instances(names, ctx)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Envelopes each allowed tool (per-call timeout + side-effect recording).
|
|
72
|
+
# The system LoadSkill (configure_chat) is NOT enveloped — it is a system
|
|
73
|
+
# tool with no side-effect and of trivial latency.
|
|
74
|
+
def wrap_tools(tools, state, skip_side_effects = [])
|
|
75
|
+
timeout = state.profile.limits[:tool_timeout] || 60
|
|
76
|
+
install_tool_gate(state)
|
|
77
|
+
tools.map do |tool|
|
|
78
|
+
ToolEnvelope.new(tool, state: state, checkpoint_store: @checkpoint_store,
|
|
79
|
+
tool_registry: @tool_registry, timeout: timeout,
|
|
80
|
+
skip_side_effects: skip_side_effects,
|
|
81
|
+
trace_recorder: @tool_trace_store)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# the model decides the fan-out, so without a cap a batch of 15
|
|
88
|
+
# data-tools is 15 simultaneous requests to the same upstream — which is how
|
|
89
|
+
# one turn earns a 429 for every other turn in the process. Note the contrast
|
|
90
|
+
# with the primitives that already shipped: `spawn_subagents` caps at 8
|
|
91
|
+
# (SubagentGraph.fan_out_cap) and `Tools::Concurrency.gather` takes `max:`.
|
|
92
|
+
#
|
|
93
|
+
# The cap is enforced in OUR decorator, not in the gem (which has none): ONE
|
|
94
|
+
# semaphore per turn, installed here — once, in the turn's own fiber, before
|
|
95
|
+
# any tool can run — and acquired by every ToolEnvelope. Off (nil) unless the
|
|
96
|
+
# turn actually got concurrency, so the serial path allocates nothing.
|
|
97
|
+
# A state that predates these fields (a unit stub) is left alone.
|
|
98
|
+
def install_tool_gate(state)
|
|
99
|
+
return unless state.respond_to?(:tool_gate) && state.respond_to?(:tool_concurrency)
|
|
100
|
+
|
|
101
|
+
cap = state.tool_concurrency
|
|
102
|
+
state.tool_gate = cap ? Async::Semaphore.new(cap) : nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Real Engine -> Entries (respond to factory); fakes -> ready instances.
|
|
106
|
+
# `turn_context` is deposited into the instances that expose it
|
|
107
|
+
# (data-tools); the rest ignore it (parity).
|
|
108
|
+
def instantiate_tools(allowed, turn_context = nil)
|
|
109
|
+
Array(allowed).map do |t|
|
|
110
|
+
tool = t.respond_to?(:factory) ? t.factory.call : t
|
|
111
|
+
inject_turn_context(tool, turn_context)
|
|
112
|
+
tool
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# seam: deposits the turn context into the freshly created instance
|
|
117
|
+
# (same idea as `remember`, which receives tenant/state) BEFORE the
|
|
118
|
+
# ToolEnvelope. Duck-typed: only what exposes `turn_context=` (DataDefinedTool)
|
|
119
|
+
# receives it. nil (a state with no turn_context, e.g. a test stub) -> no-op.
|
|
120
|
+
def inject_turn_context(tool, turn_context)
|
|
121
|
+
return if turn_context.nil?
|
|
122
|
+
|
|
123
|
+
tool.turn_context = turn_context if tool.respond_to?(:turn_context=)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# impl_name -> Capability::ResolvedTool(capability_name:), STILL without
|
|
127
|
+
# ToolEnvelope (the call site's wrap_tools wraps the whole set — same
|
|
128
|
+
# order impl -> ResolvedTool -> ToolEnvelope). entry already validated in
|
|
129
|
+
# resolve_capabilities.
|
|
130
|
+
def capability_tool_instances(names, turn_context = nil)
|
|
131
|
+
names.map do |impl_name, capability_name|
|
|
132
|
+
entry = @tool_registry.entries.find { |e| e.name == impl_name }
|
|
133
|
+
tool = entry.factory.call
|
|
134
|
+
inject_turn_context(tool, turn_context)
|
|
135
|
+
Capability::ResolvedTool.new(tool, capability_name: capability_name,
|
|
136
|
+
impl_name: impl_name)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Insika
|
|
4
|
+
# "Level 1" (progressive disclosure) view of the tools: just
|
|
5
|
+
# name+description, the analog of SkillCatalog for tools. It does NOT read
|
|
6
|
+
# disk (the tools are already in the ToolRegistry, registered at boot) and does NOT
|
|
7
|
+
# inherit from RubyLLM::Tool — duck typing over `.description` (pure, testable without
|
|
8
|
+
# the gem).
|
|
9
|
+
#
|
|
10
|
+
# The canonical `description` does not live in the Entry (Registry::Entry has no such
|
|
11
|
+
# field): it comes from the tool INSTANCE (`factory.call.description`). That is why the
|
|
12
|
+
# catalog is LAZY: it only instantiates the tools on the first query (`all`), not at
|
|
13
|
+
# boot. A deployment without `tools_deferred` never touches the catalog and pays for
|
|
14
|
+
# no instantiation at all; a broken factory surfaces on first use (where the
|
|
15
|
+
# Executor would also catch it at stage 3), not at construction.
|
|
16
|
+
class ToolCatalog
|
|
17
|
+
Entry = Data.define(:name, :description)
|
|
18
|
+
|
|
19
|
+
def initialize(tool_registry:)
|
|
20
|
+
@tool_registry = tool_registry
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def all
|
|
24
|
+
@entries ||= build_entries
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Reloads the index (after authoring a data-tool in the overlay). Mirrors
|
|
28
|
+
# SkillCatalog#reload — level-1/tool_search starts seeing the new tool without a
|
|
29
|
+
# restart. An in-flight turn has already captured `all`.
|
|
30
|
+
def reload
|
|
31
|
+
@entries = build_entries
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Allowed deferred slice (typically allowed_tools ∩ tools_deferred).
|
|
36
|
+
# Names outside the catalog are silently ignored (safe failure: less
|
|
37
|
+
# exposure, never more).
|
|
38
|
+
def subset(names)
|
|
39
|
+
wanted = Array(names).map(&:to_s)
|
|
40
|
+
all.select { |e| wanted.include?(e.name) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# PURE matcher: case-insensitive, substring/keyword, NO
|
|
44
|
+
# embeddings. name weighs 2, description weighs 1; ties broken by original index
|
|
45
|
+
# (Ruby's sort_by is not stable). `within:` restricts the universe via subset.
|
|
46
|
+
def search(query, within: nil)
|
|
47
|
+
terms = query.to_s.downcase.split(/\s+/).reject(&:empty?)
|
|
48
|
+
return [] if terms.empty?
|
|
49
|
+
|
|
50
|
+
universe = within ? subset(within) : all
|
|
51
|
+
scored = universe.each_with_index.filter_map do |entry, idx|
|
|
52
|
+
score = score_entry(entry, terms)
|
|
53
|
+
[entry, score, idx] if score.positive?
|
|
54
|
+
end
|
|
55
|
+
scored.sort_by { |_entry, score, idx| [-score, idx] }.map(&:first)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Level 1 injected into the prompt — mirror of SkillCatalog#format_for_prompt,
|
|
59
|
+
# swapping the tag and the final instruction (load_skill -> tool_search).
|
|
60
|
+
def format_for_prompt(entries = all)
|
|
61
|
+
return "" if entries.empty?
|
|
62
|
+
|
|
63
|
+
lines = entries.map { |e| %( <tool name="#{e.name}">#{e.description}</tool>) }.join("\n")
|
|
64
|
+
|
|
65
|
+
<<~PROMPT.strip
|
|
66
|
+
<available_tools>
|
|
67
|
+
#{lines}
|
|
68
|
+
</available_tools>
|
|
69
|
+
|
|
70
|
+
Before using a tool above, call `tool_search` with what you need
|
|
71
|
+
to do to enable it in this conversation.
|
|
72
|
+
PROMPT
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_entries
|
|
78
|
+
@tool_registry.entries.map do |entry|
|
|
79
|
+
Entry.new(name: entry.name, description: entry.factory.call.description.to_s)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def score_entry(entry, terms)
|
|
84
|
+
name = entry.name.downcase
|
|
85
|
+
desc = entry.description.downcase
|
|
86
|
+
terms.sum { |term| (name.include?(term) ? 2 : 0) + (desc.include?(term) ? 1 : 0) }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|