Class: Boxcars::ToolTrain

Inherits:
Train show all
Defined in:
lib/boxcars/train/tool_train.rb

Overview

A Train runtime that uses native LLM tool-calling instead of text ReAct parsing.

Direct Known Subclasses

StationAgent

Defined Under Namespace

Classes: MessagePrompt

Constant Summary collapse

DEFAULT_NAME =
"Tool Calling Train"
DEFAULT_DESCRIPTION =
"Train that uses native tool-calling when supported by the engine."
CTEMPLATE =
[
  syst(
    "Answer the user's question using the provided tools when helpful.\n",
    "If a tool is needed, call it with valid arguments.\n",
    "If no tool is needed, answer directly.\n",
    "Available tools:\n",
    "%<boxcar_descriptions>s\n",
    "%<next_actions>s"
  ),
  hist,
  user("%<input>s")
].freeze

Constants inherited from Boxcar

Boxcar::SCHEMA_KEY_ALIASES, Boxcar::TYPE_ALIASES

Instance Attribute Summary collapse

Attributes inherited from Train

#boxcars, #early_stopping_method, #engine_prefix, #final_answer_prefix, #max_iterations, #name_to_boxcar_map, #observation_prefix, #question_prefix, #return_intermediate_steps, #return_values, #thought_prefix, #using_xml

Attributes inherited from EngineBoxcar

#engine, #prompt, #stop, #top_k

Attributes inherited from Boxcar

#description, #name, #parameters, #return_direct

Instance Method Summary collapse

Methods inherited from Train

#boxcar_descriptions, #boxcar_names, #construct_scratchpad, #extract_boxcar_and_input, #finish_boxcar_name, #get_boxcar_result, #get_next_action, #init_prefixes, #input_keys, #key_and_value_text, #next_actions, #observation_text, #output_keys, #plan, #pre_return, #prepare_for_new_call, #return_stopped_response, #should_continue?

Methods inherited from EngineBoxcar

#apply, #extract_code, #generate, #input_keys, #output_key, #output_keys, #predict, #prediction_variables

Methods inherited from Boxcar

#apply, assi, #conduct, #conduct_result, hist, #input_keys, #output_keys, #parameters_json_schema, #run, #run_result, #schema, syst, #tool_call_name, #tool_definition, #tool_spec, user, #validate_inputs, #validate_outputs

Constructor Details

#initialize(boxcars:, engine: nil, name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompt: nil, **kwargs) ⇒ ToolTrain

Returns a new instance of ToolTrain.



53
54
55
56
57
# File 'lib/boxcars/train/tool_train.rb', line 53

def initialize(boxcars:, engine: nil, name: DEFAULT_NAME, description: DEFAULT_DESCRIPTION, prompt: nil, **kwargs)
  @wants_next_actions = kwargs.fetch(:wants_next_actions, false)
  prompt ||= my_prompt
  super(boxcars:, engine:, prompt:, name:, description:, **kwargs)
end

Instance Attribute Details

#wants_next_actionsObject

Returns the value of attribute wants_next_actions.



8
9
10
# File 'lib/boxcars/train/tool_train.rb', line 8

def wants_next_actions
  @wants_next_actions
end

Instance Method Details

#call(inputs:) ⇒ Object

Tool-calling runtime loop; does not use text parsing.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/boxcars/train/tool_train.rb', line 64

def call(inputs:)
  prepare_for_new_call
  ensure_tool_calling_engine!

  conversation_messages = initial_messages(inputs)
  tool_specs = boxcars.map(&:tool_spec)
  intermediate_steps = []
  iterations = 0
  responses_state = responses_runtime? ? { previous_response_id: nil, response_input: nil } : nil

  while should_continue?(iterations)
    response = normalize_response_payload(tool_call_response(conversation_messages, tool_specs, responses_state:))
    assistant_message = extract_assistant_message(response)
    conversation_messages << assistant_message

    tool_calls = assistant_message[:tool_calls]
    if tool_calls.is_a?(Array) && !tool_calls.empty?
      response_tool_outputs = []
      tool_calls.each do |tool_call|
        action, observation, tool_message, return_direct = execute_tool_call(tool_call)
        intermediate_steps << [action, observation]
        Boxcars.debug "Observation: #{observation}", :green

        return pre_return(TrainFinish.new({ return_values[0] => observation }, log: ""), intermediate_steps) if return_direct

        conversation_messages << tool_message
        response_tool_outputs << responses_tool_output_item(tool_call, observation) if responses_state
      end

      if responses_state
        responses_state[:previous_response_id] = response[:id]
        responses_state[:response_input] = response_tool_outputs
      end

      iterations += 1
      next
    end

    responses_state[:response_input] = nil if responses_state

    final_text = extract_assistant_text(assistant_message)
    output = TrainFinish.new({ output: final_text }, log: final_text)
    return pre_return(output, intermediate_steps)
  end

  pre_return(TrainFinish.new({ output: "Agent stopped due to max iterations." }, log: ""), intermediate_steps)
end

#prediction_additional(_inputs) ⇒ Object



59
60
61
# File 'lib/boxcars/train/tool_train.rb', line 59

def prediction_additional(_inputs)
  { boxcar_descriptions:, next_actions: }.merge(super)
end