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.
264 265 266 |
# File 'lib/little_ghost/tool.rb', line 264 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") }
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/little_ghost/tool.rb', line 206 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.
165 166 167 168 169 |
# File 'lib/little_ghost/tool.rb', line 165 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.
193 194 195 196 197 |
# File 'lib/little_ghost/tool.rb', line 193 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.
179 180 181 182 183 184 185 186 |
# File 'lib/little_ghost/tool.rb', line 179 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.
228 229 230 231 232 233 234 |
# File 'lib/little_ghost/tool.rb', line 228 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.
154 155 156 157 158 |
# File 'lib/little_ghost/tool.rb', line 154 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.
284 285 |
# File 'lib/little_ghost/tool.rb', line 284 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.
325 326 327 |
# File 'lib/little_ghost/tool.rb', line 325 def call(_input) raise AbstractMethodError, "#{self.class} must implement #call" end |
#close ⇒ Object
Releases resources owned by this tool. Subclasses may override it.
330 331 |
# File 'lib/little_ghost/tool.rb', line 330 def close end |
#description ⇒ Object
Model-visible description declared by the tool class.
269 270 |
# File 'lib/little_ghost/tool.rb', line 269 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.
275 |
# File 'lib/little_ghost/tool.rb', line 275 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.
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/little_ghost/tool.rb', line 301 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 value = bound_for(context).call(input) return normalize_execution_result(value) if value.is_a?(ExecutionResult) success(sanitize(value)) 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.
271 272 |
# File 'lib/little_ghost/tool.rb', line 271 def input_schema = self.class.input_schema # Frozen provider-facing tool specification. |
#model ⇒ Object
Bound model, when available.
290 291 |
# File 'lib/little_ghost/tool.rb', line 290 def model = binding.model # Bound workspace, when available. |
#run ⇒ Object
Bound run, when available.
286 287 |
# File 'lib/little_ghost/tool.rb', line 286 def run = binding.run # Bound runtime, when available. |
#runtime ⇒ Object
Bound runtime, when available.
288 289 |
# File 'lib/little_ghost/tool.rb', line 288 def runtime = binding.runtime # Bound model, when available. |
#sandbox ⇒ Object
Bound sandbox, when available.
294 |
# File 'lib/little_ghost/tool.rb', line 294 def sandbox = binding.sandbox |
#specification ⇒ Object
Frozen provider-facing tool specification.
273 274 |
# File 'lib/little_ghost/tool.rb', line 273 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.
267 268 |
# File 'lib/little_ghost/tool.rb', line 267 def tool_name = self.class.tool_name # Model-visible description declared by the tool class. |
#workspace ⇒ Object
Bound workspace, when available.
292 293 |
# File 'lib/little_ghost/tool.rb', line 292 def workspace = binding.workspace # Bound sandbox, when available. |