Class: LittleGhost::Tool
- Inherits:
-
Object
- Object
- LittleGhost::Tool
- Extended by:
- Support::ClassAttributes
- Defined in:
- lib/little_ghost/tool.rb
Overview
Give an agent a validated way to call application code. Every tool declares a model-visible name, description, and input shape before implementing its operation.
class TicketStatusTool < LittleGhost::Tool
tool_name "ticket_status"
description "Look up a support ticket's status."
input_schema type: "object", properties: {
ticket_id: {type: "string"}
}, required: ["ticket_id"], additionalProperties: false
def call(input)
{ticket_id: input.fetch("ticket_id"), status: "waiting_on_customer"}
end
end
result = TicketStatusTool.new.execute("ticket_id" => "SUP-481")
result.success? # => true
JSON.parse(result.content) # => {"ticket_id"=>"SUP-481", "status"=>"waiting_on_customer"}
The class DSL produces the frozen specification sent to models. execute
validates incoming arguments, invokes call, and normalizes strings,
JSON-compatible collections, and other return values to model-facing text.
Tool.define offers the same contract for an embedded implementation.
A tool registry creates one instance per agent run and supplies a Binding for
access to the agent, run, runtime, model, workspace, and sandbox. Mutable
per-instance state therefore belongs to that run. Registries close tools that
implement close; exclusive true serializes calls against every
other exclusive tool in the same run.
Validation and application ToolError failures become error results. A ToolError message is visible to the model and must be safe to disclose; unexpected exception messages are replaced with their class name. Cancellation, deadlines, and cleanup errors propagate instead of becoming ordinary tool output. The configured sandbox, not Tool itself, enforces filesystem and process isolation.
Direct Known Subclasses
LittleGhost::Tools::Filesystem::ListFiles, LittleGhost::Tools::Filesystem::ReadFile, LittleGhost::Tools::Filesystem::ReplaceInFile, LittleGhost::Tools::Filesystem::WriteFile, LittleGhost::Tools::Shell, LittleGhost::Tools::WriteTodos
Defined Under Namespace
Classes: Binding, ExecutionResult, SchemaValidator
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
RunContext supplied to the current #execute call, or nil outside execution.
Class Method Summary collapse
-
.define(name:, description:, input_schema: {}, &implementation) ⇒ Object
Creates an anonymous Tool subclass backed by
implementation. -
.description(*values) ⇒ Object
:call-seq: description() -> String description(value) -> value.
-
.exclusive(*values) ⇒ Object
:call-seq: exclusive() -> true or false exclusive(value) -> value.
-
.input_schema(*values) ⇒ Object
:call-seq: input_schema() -> Hash input_schema(schema) -> schema.
-
.specification ⇒ Object
The frozen model-facing name, description, and input schema.
-
.tool_name(*values) ⇒ Object
:call-seq: tool_name() -> String tool_name(value) -> value.
Instance Method Summary collapse
-
#agent ⇒ Object
Bound agent, when the tool belongs to an agent run.
-
#call(_input) ⇒ Object
Implements the model-requested operation.
-
#close ⇒ Object
Releases resources owned by this tool.
-
#description ⇒ Object
Model-visible description declared by the tool class.
-
#exclusive? ⇒ Boolean
Indicates whether calls use the run-wide exclusive-tool lock.
-
#execute(input, context: RunContext.new) ⇒ Object
Validates
inputand invokes the tool, returning an ExecutionResult. -
#initialize(binding: Binding.new) ⇒ Tool
constructor
Creates a tool with the run-scoped collaborators in
binding. -
#input_schema ⇒ Object
Normalized JSON input schema declared by the tool class.
-
#model ⇒ Object
Bound model, when available.
-
#run ⇒ Object
Bound run, when available.
-
#runtime ⇒ Object
Bound runtime, when available.
-
#sandbox ⇒ Object
Bound sandbox, when available.
-
#specification ⇒ Object
Frozen provider-facing tool specification.
-
#tool_name ⇒ Object
Model-visible name declared by the tool class.
-
#workspace ⇒ Object
Bound workspace, when available.
Methods included from Support::ClassAttributes
Constructor Details
Instance Attribute Details
#context ⇒ Object
RunContext supplied to the current #execute call, or nil outside execution.
250 251 252 |
# File 'lib/little_ghost/tool.rb', line 250 def context @context end |
Class Method Details
.define(name:, description:, input_schema: {}, &implementation) ⇒ Object
Creates an anonymous Tool subclass backed by implementation.
The block receives input and may also accept the context: keyword.
tool = LittleGhost::Tool.define(
name: "echo", description: "Echo text.",
input_schema: {type: "object"}
) { |input| input.fetch("text") }
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/little_ghost/tool.rb', line 192 def define(name:, description:, input_schema: {}, &implementation) raise ArgumentError, "A tool implementation block is required" unless implementation Class.new(self) do tool_name(name) description(description) input_schema(input_schema) define_method(:call) do |input| accepts_context = implementation.parameters.any? do |kind, parameter| kind == :keyrest || (%i[key keyreq].include?(kind) && parameter == :context) end if accepts_context implementation.call(input, context: context) else implementation.call(input) end end end end |
.description(*values) ⇒ Object
:call-seq:
description() -> String
description(value) -> value
The model-visible description used to decide when the tool applies.
151 152 153 154 155 |
# File 'lib/little_ghost/tool.rb', line 151 def description(*values) return description_value if values.empty? self.description_value = String(values.fetch(0)).freeze end |
.exclusive(*values) ⇒ Object
:call-seq:
exclusive() -> true or false
exclusive(value) -> value
Whether calls acquire the run-wide exclusive tool lock.
179 180 181 182 183 |
# File 'lib/little_ghost/tool.rb', line 179 def exclusive(*values) return !!exclusive_value if values.empty? self.exclusive_value = !!values.fetch(0) end |
.input_schema(*values) ⇒ Object
:call-seq:
input_schema() -> Hash
input_schema(schema) -> schema
The frozen JSON Schema subset used to validate model input.
Setting a non-Hash schema raises ArgumentError. Keys are normalized to strings and the entire value is deeply frozen.
165 166 167 168 169 170 171 172 |
# File 'lib/little_ghost/tool.rb', line 165 def input_schema(*values) return input_schema_value || {}.freeze if values.empty? value = values.fetch(0) raise ArgumentError, "input_schema must be a hash" unless value.is_a?(Hash) self.input_schema_value = deep_freeze(value) end |
.specification ⇒ Object
The frozen model-facing name, description, and input schema.
214 215 216 217 218 219 220 |
# File 'lib/little_ghost/tool.rb', line 214 def specification { name: tool_name, description: description, input_schema: input_schema }.freeze end |
.tool_name(*values) ⇒ Object
:call-seq:
tool_name() -> String
tool_name(value) -> value
The model-visible tool name.
Named classes derive a snake-cased default; passing value replaces it.
140 141 142 143 144 |
# File 'lib/little_ghost/tool.rb', line 140 def tool_name(*values) return configured_name if values.empty? self.tool_name_value = String(values.fetch(0)).freeze end |
Instance Method Details
#agent ⇒ Object
Bound agent, when the tool belongs to an agent run.
270 271 |
# File 'lib/little_ghost/tool.rb', line 270 def agent = binding.agent # Bound run, when available. |
#call(_input) ⇒ Object
Implements the model-requested operation.
Subclasses must override this method. The current RunContext is available
through context while the call executes.
308 309 310 |
# File 'lib/little_ghost/tool.rb', line 308 def call(_input) raise AbstractMethodError, "#{self.class} must implement #call" end |
#close ⇒ Object
Releases resources owned by this tool. Subclasses may override it.
313 314 |
# File 'lib/little_ghost/tool.rb', line 313 def close end |
#description ⇒ Object
Model-visible description declared by the tool class.
255 256 |
# File 'lib/little_ghost/tool.rb', line 255 def description = self.class.description # Normalized JSON input schema declared by the tool class. |
#exclusive? ⇒ Boolean
Indicates whether calls use the run-wide exclusive-tool lock.
261 |
# File 'lib/little_ghost/tool.rb', line 261 def exclusive? = self.class.exclusive |
#execute(input, context: RunContext.new) ⇒ Object
Validates input and invokes the tool, returning an ExecutionResult.
Cancellation, deadline, and cleanup exceptions remain control-flow exceptions. ToolError and unexpected failures become sanitized error results; unexpected exception messages are not exposed to the model.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/little_ghost/tool.rb', line 287 def execute(input, context: RunContext.new) context ||= RunContext.new errors = SchemaValidator.new(self.class.input_schema).validate(input) unless errors.empty? = "Invalid tool input: #{errors.join("; ")}" return failure(, error: ToolError.new()) end success(sanitize(bound_for(context).call(input))) rescue CancelledError, DeadlineExceededError, CleanupError raise rescue ToolError => error failure(error., error:) rescue => error failure("Tool failed (#{error.class})", error:) end |
#input_schema ⇒ Object
Normalized JSON input schema declared by the tool class.
257 258 |
# File 'lib/little_ghost/tool.rb', line 257 def input_schema = self.class.input_schema # Frozen provider-facing tool specification. |
#model ⇒ Object
Bound model, when available.
276 277 |
# File 'lib/little_ghost/tool.rb', line 276 def model = binding.model # Bound workspace, when available. |
#run ⇒ Object
Bound run, when available.
272 273 |
# File 'lib/little_ghost/tool.rb', line 272 def run = binding.run # Bound runtime, when available. |
#runtime ⇒ Object
Bound runtime, when available.
274 275 |
# File 'lib/little_ghost/tool.rb', line 274 def runtime = binding.runtime # Bound model, when available. |
#sandbox ⇒ Object
Bound sandbox, when available.
280 |
# File 'lib/little_ghost/tool.rb', line 280 def sandbox = binding.sandbox |
#specification ⇒ Object
Frozen provider-facing tool specification.
259 260 |
# File 'lib/little_ghost/tool.rb', line 259 def specification = self.class.specification # Indicates whether calls use the run-wide exclusive-tool lock. |
#tool_name ⇒ Object
Model-visible name declared by the tool class.
253 254 |
# File 'lib/little_ghost/tool.rb', line 253 def tool_name = self.class.tool_name # Model-visible description declared by the tool class. |
#workspace ⇒ Object
Bound workspace, when available.
278 279 |
# File 'lib/little_ghost/tool.rb', line 278 def workspace = binding.workspace # Bound sandbox, when available. |