Module: Btape::LLM::Prompt
- Defined in:
- lib/btape/llm/prompt.rb
Overview
What the model is told before it is asked for a tape.
The command list and the settings table are built from Parser and Settings rather than written out again here, so a command added to the language is a command the model is told about, and a setting cannot be described to it with a default it no longer has.
Constant Summary collapse
- RULES =
[ 'Answer with the contents of a .tape file and nothing else: no explanation, no code fences.', 'The script must contain exactly one Output line, naming a .gif file, and it comes first.', 'One command per line. Lines starting with # are comments.', 'The line is split like a shell command, so any argument containing a space must be quoted ' \ 'with double quotes.', 'Click, WaitFor and Frame take a CSS selector, or text=Some text to match on visible text.', 'Durations are a number followed by ms or s, such as 500ms or 1.5s.', 'Prefer WaitFor or WaitForJS over Sleep for anything the page has to finish doing; ' \ 'Sleep is for holding a finished frame on screen long enough to be seen.', 'Use only the commands and settings listed above. Do not invent either, ' \ 'and do not use a shell, a comment or a blank line to stand in for one.' ].freeze
- EXAMPLE =
<<~TAPE # Signing in, recorded at half size Output signin.gif Viewport 1280x720 Set Scale 0.5 Goto http://localhost:3000/signin WaitFor "#email" Type "#email" "demo@example.com" Type "#password" "correct horse" Click "text=Sign in" WaitFor "text=Welcome back" 5s Sleep 2s TAPE
- VALUES =
What a setting takes, as the coercions in Settings enforce it. A default is shown as a tape would have to write it rather than as Settings holds it, since
Set FrameDelay 0.1is not something the parser would accept back. { duration: 'a duration', url: "a #{Settings::URL_SCHEMES.join(', ')} url", count: 'a whole number, 0 or more', positive_integer: 'a whole number, 1 or more', positive_float: 'a number' }.freeze
Class Method Summary collapse
- .commands ⇒ Object
-
.indent(text) ⇒ Object
Blank lines are left alone, so that indenting a block into the prompt does not leave trailing whitespace through the middle of it.
-
.literal(coercion, default) ⇒ Object
A coerced default as a tape would write it: durations are held in seconds and go back to the ms or s they were read from.
-
.repair(reason) ⇒ Object
A tape that did not parse goes back with the parser's own complaint, which names the line and what was wrong with it.
- .settings ⇒ Object
- .system ⇒ Object
- .user(description, context: nil) ⇒ Object
Class Method Details
.commands ⇒ Object
106 107 108 |
# File 'lib/btape/llm/prompt.rb', line 106 def commands Parser::SIGNATURES.map { |name, arguments| "#{name} #{arguments}".strip }.join("\n") end |
.indent(text) ⇒ Object
Blank lines are left alone, so that indenting a block into the prompt does not leave trailing whitespace through the middle of it.
128 129 130 |
# File 'lib/btape/llm/prompt.rb', line 128 def indent(text) text.strip.gsub(/^(?=.)/, ' ') end |
.literal(coercion, default) ⇒ Object
A coerced default as a tape would write it: durations are held in seconds and go back to the ms or s they were read from.
119 120 121 122 123 124 |
# File 'lib/btape/llm/prompt.rb', line 119 def literal(coercion, default) return default.to_s unless coercion == :duration return "#{(default * 1000).round}ms" if default < 1 "#{default.to_i == default ? default.to_i : default}s" end |
.repair(reason) ⇒ Object
A tape that did not parse goes back with the parser's own complaint, which names the line and what was wrong with it. Saying so beats asking again and hoping, since the model can see what it wrote.
98 99 100 101 102 103 104 |
# File 'lib/btape/llm/prompt.rb', line 98 def repair(reason) <<~PROMPT btape rejected that tape: #{reason} Answer with the whole corrected tape, and nothing else. PROMPT end |
.settings ⇒ Object
110 111 112 113 114 115 |
# File 'lib/btape/llm/prompt.rb', line 110 def settings Settings::DEFINITIONS.map do |name, (_attribute, coercion, default)| takes = coercion.is_a?(Array) ? coercion.join('|') : VALUES.fetch(coercion) "Set #{name} <#{takes}>#{" — #{literal(coercion, default)} by default" unless default.nil?}" end.join("\n") end |
.system ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/btape/llm/prompt.rb', line 58 def system <<~PROMPT You write btape scripts. btape runs a .tape file against Chromium and records the run as an animated GIF, so a tape is a short, deliberate demonstration rather than a test: it moves at a pace somebody can watch. The commands, one per line: #{indent(commands)} A run is configured with `Set NAME VALUE`: #{indent(settings)} Rules: #{indent(RULES.map { |rule| "- #{rule}" }.join("\n"))} An example of a whole tape: #{indent(EXAMPLE)} PROMPT end |
.user(description, context: nil) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/btape/llm/prompt.rb', line 82 def user(description, context: nil) return description if context.nil? || context.strip.empty? <<~PROMPT #{description} Context about the page being recorded — prefer the selectors it names over any you would otherwise guess at: #{context} PROMPT end |