Class: Boxcars::JSONEngineBoxcar

Inherits:
EngineBoxcar show all
Defined in:
lib/boxcars/boxcar/json_engine_boxcar.rb

Overview

For Boxcars that use an engine to do their work.

Constant Summary

Constants inherited from Boxcar

Boxcar::SCHEMA_KEY_ALIASES, Boxcar::TYPE_ALIASES

Instance Attribute Summary collapse

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 EngineBoxcar

#apply, #call, #extract_code, #input_keys, #output_key, #output_keys, #predict, #prediction_additional, #prediction_variables

Methods inherited from Boxcar

#apply, assi, #call, #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(prompt: nil, wanted_data: nil, data_description: nil, important: nil, symbolize: false, json_schema: nil, json_schema_name: "boxcars_json_output", json_schema_strict: true, **kwargs) ⇒ JSONEngineBoxcar

Returns a new instance of JSONEngineBoxcar.

Parameters:

  • prompt (Boxcars::Prompt) (defaults to: nil)

    The prompt to use for this boxcar with sane defaults.

  • wanted_data (String) (defaults to: nil)

    The data to extract from.

  • data_description (String) (defaults to: nil)

    The description of the data.

  • important (String) (defaults to: nil)

    Any important instructions you want to give the LLM.

  • symbolize (Boolean) (defaults to: false)

    Symbolize the JSON results if true

  • kwargs (Hash)

    Additional arguments

  • json_schema (Hash, nil) (defaults to: nil)

    Optional JSON Schema used to validate parsed output.

  • json_schema_name (String) (defaults to: "boxcars_json_output")

    Name used when rendering schema instructions.

  • json_schema_strict (Boolean) (defaults to: true)

    If true, reject outputs that violate schema.



20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 20

def initialize(prompt: nil, wanted_data: nil, data_description: nil, important: nil, symbolize: false,
               json_schema: nil, json_schema_name: "boxcars_json_output", json_schema_strict: true, **kwargs)
  @wanted_data = wanted_data || "summarize the pertinent facts from the input data"
  @data_description = data_description || "the input data"
  @important = important
  @json_schema = json_schema
  @json_schema_name = json_schema_name
  @json_schema_strict = json_schema_strict
  the_prompt = prompt || default_prompt
  kwargs[:description] ||= "JSON Engine Boxcar"
  @symbolize = symbolize
  super(prompt: the_prompt, **kwargs)
end

Instance Attribute Details

#data_descriptionObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def data_description
  @data_description
end

#importantObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def important
  @important
end

#json_schemaObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def json_schema
  @json_schema
end

#json_schema_nameObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def json_schema_name
  @json_schema_name
end

#json_schema_strictObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def json_schema_strict
  @json_schema_strict
end

#symbolizeObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def symbolize
  @symbolize
end

#wanted_dataObject

A JSON Engine Boxcar is a container for a single tool to run.



8
9
10
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 8

def wanted_data
  @wanted_data
end

Instance Method Details

#default_promptObject



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 34

def default_prompt
  stock_prompt = <<~SYSPR
    I will provide you with %<data_description>s.
    Your job is to extract information as described below.

    Your Output must be valid JSON with no lead in or post answer text in the output format below:

    Output Format:
      {
      %<wanted_data>s
      }
  SYSPR
  stock_prompt += "\nYour output MUST conform to this JSON Schema:\n```json\n#{render_json_schema}\n```\n" if json_schema
  stock_prompt += "\n\nImportant:\n#{important}\n" unless important.to_s.empty?

  sprompt = format(stock_prompt, wanted_data:, data_description:)
  ctemplate = [
    Boxcar.syst(sprompt),
    Boxcar.user("%<input>s")
  ]
  conv = Conversation.new(lines: ctemplate)
  ConversationPrompt.new(conversation: conv, input_variables: [:input], other_inputs: [], output_variables: [:answer])
end

#extract_answer(data) ⇒ Result

get answer from parsed JSON

Parameters:

  • data (Hash)

    The data to extract from.

Returns:



107
108
109
110
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 107

def extract_answer(data)
  reply = data
  Result.new(status: :ok, answer: reply, explanation: reply)
end

#generate(input_list:, current_conversation: nil) ⇒ Object

Use native structured output when the engine supports it; otherwise fall back to the existing prompt-driven JSON extraction flow.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 60

def generate(input_list:, current_conversation: nil)
  return super unless native_json_schema_generation_supported?
  raise Boxcars::ArgumentError, "#{self.class}#generate requires at least one input hash" if input_list.empty?

  stop = input_value(input_list.first, :stop)
  the_prompt = current_conversation ? prompt.with_conversation(current_conversation) : prompt
  generations = []
  raw_responses = []

  input_list.each do |inputs|
    raw = engine.client(
      prompt: the_prompt,
      inputs: inputs,
      **native_json_generation_kwargs(stop:)
    )
    raw_responses << raw
    generations << [Generation.new(text: extract_text_from_engine_response(raw), generation_info: {})]
  end

  EngineResult.new(
    generations: generations,
    engine_output: { raw_responses:, native_structured_output: true }
  )
end

#get_answer(engine_output) ⇒ Result

Parse out the action and input from the engine output.

Parameters:

  • engine_output (String)

    The output from the engine.

Returns:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/boxcars/boxcar/json_engine_boxcar.rb', line 88

def get_answer(engine_output)
  json_string = extract_json(engine_output)
  reply = JSON.parse(json_string, symbolize_names: symbolize)
  validation_errors = validate_against_json_schema(reply)
  if validation_errors.any? && json_schema_strict
    return Result.from_error("JSON schema validation error: #{validation_errors.join('; ')}")
  end

  Result.new(status: :ok, answer: reply, explanation: reply)
rescue JSON::ParserError => e
  Boxcars.debug "JSON: #{engine_output}", :red
  Result.from_error("JSON parsing error: #{e.message}")
rescue StandardError => e
  Result.from_error("Unexpected error: #{e.message}")
end