Class: Xeno::Tool
- Inherits:
-
RubyLLM::Tool
- Object
- RubyLLM::Tool
- Xeno::Tool
- Defined in:
- lib/xeno/tool.rb
Overview
Base class for agent tools. Anyone who knows RubyLLM already knows how to write these; xeno adds discovery and (later) the approval macro and session context.
# agent/tools/get_weather.rb
class Xeno::Tools::GetWeather < Xeno::Tool
description "Return current weather for a city."
parameter :city, description: "City name"
def execute(city:)
{ city: city, condition: "Sunny" }
end
end
Direct Known Subclasses
Instance Attribute Summary collapse
-
#session ⇒ Object
The session this call is running in — set by the runner before
call.
Class Method Summary collapse
-
.approval(policy = nil) ⇒ Object
The human-in-the-loop gate.
-
.tool_name ⇒ Object
The runtime name the model calls this tool by.
Instance Method Summary collapse
-
#name ⇒ Object
RubyLLM derives the wire name from the full class name, which would leak the namespace (xeno/tools/get_weather).
-
#state ⇒ Object
The session-scoped KV store: JSON-typed values that survive restarts, never cross sessions, and are cleared by reset.
Instance Attribute Details
#session ⇒ Object
The session this call is running in — set by the runner before
call. nil when the tool is exercised outside a session (unit tests
calling Tool.new.call directly).
56 57 58 |
# File 'lib/xeno/tool.rb', line 56 def session @session end |
Class Method Details
.approval(policy = nil) ⇒ Object
The human-in-the-loop gate. Anything irreversible or externally visible should be gated — approvals are the guardrail in an in-app-tools trust model.
approval :never # default: runs without asking
approval :once # asks the first time in a session, then remembered
approval :always # asks on every call
approval ->(ctx) { ctx.principal&.dig("role") != "admin" }
The lambda receives an ApprovalContext (session, turn, tool_name, arguments, principal); truthy means approval is required.
39 40 41 42 43 44 |
# File 'lib/xeno/tool.rb', line 39 def approval(policy = nil) @approval = policy unless policy.nil? return @approval if defined?(@approval) && @approval superclass.respond_to?(:approval) ? superclass.approval : :never end |
.tool_name ⇒ Object
The runtime name the model calls this tool by. The path supplies the
name: Zeitwerk guarantees agent/tools/get_weather.rb
defines Xeno::Tools::GetWeather, so the demodulized class name and
the file basename are the same thing. No suffix-stripping: a
charge_card_tool.rb must be callable as charge_card_tool, or the
runtime's slug-keyed lookup misses and the approval gate is skipped.
24 25 26 |
# File 'lib/xeno/tool.rb', line 24 def tool_name name.demodulize.underscore end |
Instance Method Details
#name ⇒ Object
RubyLLM derives the wire name from the full class name, which would leak the namespace (xeno/tools/get_weather). Use the path-derived name.
49 50 51 |
# File 'lib/xeno/tool.rb', line 49 def name self.class.tool_name end |
#state ⇒ Object
The session-scoped KV store: JSON-typed values that survive restarts, never cross sessions, and are cleared by reset. Working state, not long-term memory.
def execute(city:)
searches = state.get("searches").to_i + 1
state.update("searches" => searches, "last_city" => city)
...
end
67 68 69 70 71 |
# File 'lib/xeno/tool.rb', line 67 def state raise Xeno::Error, "session state is only available while running inside a session" unless session session.state end |