Class: Inquirex::LLM::Node
- Inherits:
-
Node
- Object
- Node
- Inquirex::LLM::Node
- Defined in:
- lib/inquirex/llm/node.rb
Overview
Enriched node for LLM-powered steps. Extends Inquirex::Node with attributes needed by the server-side LLM adapter: prompt template, output schema, source step references, and model configuration.
LLM verbs:
:extract — extract structured data from a free-text answer
:summarize — close the flow with a prose summary of the whole session
# :describe — generate natural-language text from structured data
# :detour — dynamically generate follow-up questions based on an answer
summarize is the one verb whose prompt the gem owns (Prompts::SUMMARIZE)
and whose answer is markdown prose rather than structured data. It must be
the last step in a flow and carries no schema and no transitions.
All LLM nodes are collecting (they produce answers) and require server round-trips. The frontend shows a "thinking" state while the server processes.
Constant Summary collapse
- LLM_VERBS =
%i[extract summarize].freeze
- NARRATIVE_VERBS =
Verbs whose result is prose for the user to read, not data for the engine to store against other steps.
%i[summarize].freeze
Instance Attribute Summary collapse
-
#fallback ⇒ Proc?
readonly
server-side fallback (stripped from JSON).
-
#from_all ⇒ Boolean
readonly
whether to pass all collected answers to the LLM.
-
#from_steps ⇒ Array<Symbol>
readonly
source step ids whose answers feed the LLM.
-
#max_tokens ⇒ Integer?
readonly
optional max output tokens.
-
#model ⇒ Symbol?
readonly
optional model hint (e.g. :claude_sonnet).
-
#prompt ⇒ String
readonly
LLM prompt template.
-
#schema ⇒ Schema?
readonly
expected output structure (required for extract).
-
#temperature ⇒ Float?
readonly
optional sampling temperature.
Class Method Summary collapse
-
.from_h(id, hash) ⇒ LLM::Node
Deserializes from a plain Hash (string or symbol keys).
-
.llm_verb?(verb) ⇒ Boolean
Whether this verb is a recognized LLM verb.
Instance Method Summary collapse
-
#collecting? ⇒ Boolean
LLM verbs always collect output (the LLM provides the "answer").
-
#display? ⇒ Boolean
LLM verbs are never display-only.
-
#initialize(prompt:, schema: nil, from_steps: [], from_all: false, model: nil, temperature: nil, max_tokens: nil, fallback: nil) ⇒ Node
constructor
A new instance of Node.
-
#llm_verb? ⇒ Boolean
Whether this is an LLM-powered step requiring server processing.
-
#narrative? ⇒ Boolean
Whether this step closes the flow with prose for the user to read.
-
#summarize? ⇒ Boolean
Whether this is the
summarizestep. -
#to_h ⇒ Hash
Serializes to a plain Hash.
Constructor Details
#initialize(prompt:, schema: nil, from_steps: [], from_all: false, model: nil, temperature: nil, max_tokens: nil, fallback: nil) ⇒ Node
Returns a new instance of Node.
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/inquirex/llm/node.rb', line 47 def initialize(prompt:, schema: nil, from_steps: [], from_all: false, model: nil, temperature: nil, max_tokens: nil, fallback: nil, **) @prompt = prompt @schema = schema @from_steps = Array(from_steps).map(&:to_sym).freeze @from_all = !!from_all @model = model&.to_sym @temperature = temperature&.to_f @max_tokens = max_tokens&.to_i @fallback = fallback super(**) end |
Instance Attribute Details
#fallback ⇒ Proc? (readonly)
server-side fallback (stripped from JSON)
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def fallback @fallback end |
#from_all ⇒ Boolean (readonly)
whether to pass all collected answers to the LLM
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def from_all @from_all end |
#from_steps ⇒ Array<Symbol> (readonly)
source step ids whose answers feed the LLM
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def from_steps @from_steps end |
#max_tokens ⇒ Integer? (readonly)
optional max output tokens
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def max_tokens @max_tokens end |
#model ⇒ Symbol? (readonly)
optional model hint (e.g. :claude_sonnet)
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def model @model end |
#prompt ⇒ String (readonly)
LLM prompt template
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def prompt @prompt end |
#schema ⇒ Schema? (readonly)
expected output structure (required for extract)
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def schema @schema end |
#temperature ⇒ Float? (readonly)
optional sampling temperature
30 31 32 |
# File 'lib/inquirex/llm/node.rb', line 30 def temperature @temperature end |
Class Method Details
.from_h(id, hash) ⇒ LLM::Node
Deserializes from a plain Hash (string or symbol keys).
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/inquirex/llm/node.rb', line 107 def self.from_h(id, hash) verb = hash["verb"] || hash[:verb] verb = :extract if verb.to_s == "clarify" # DSL alias; normalize wire format question = hash["question"] || hash[:question] text = hash["text"] || hash[:text] transitions_data = hash["transitions"] || hash[:transitions] || [] skip_if_data = hash["skip_if"] || hash[:skip_if] llm_data = hash["llm"] || hash[:llm] || {} transitions = transitions_data.map { |t| Inquirex::Transition.from_h(t) } skip_if = skip_if_data ? Inquirex::Rules::Base.from_h(skip_if_data) : nil prompt = llm_data["prompt"] || llm_data[:prompt] schema_raw = llm_data["schema"] || llm_data[:schema] from_raw = llm_data["from_steps"] || llm_data[:from_steps] || [] from_all = llm_data["from_all"] || llm_data[:from_all] || false model = llm_data["model"] || llm_data[:model] temp = llm_data["temperature"] || llm_data[:temperature] max_tok = llm_data["max_tokens"] || llm_data[:max_tokens] schema = schema_raw ? Schema.from_h(schema_raw) : nil from_steps = from_raw.map(&:to_sym) new( id:, verb:, question:, text:, transitions:, skip_if:, prompt:, schema:, from_steps:, from_all:, model:, temperature: temp, max_tokens: max_tok ) end |
.llm_verb?(verb) ⇒ Boolean
Whether this verb is a recognized LLM verb.
148 149 150 |
# File 'lib/inquirex/llm/node.rb', line 148 def self.llm_verb?(verb) LLM_VERBS.include?(verb.to_sym) end |
Instance Method Details
#collecting? ⇒ Boolean
LLM verbs always collect output (the LLM provides the "answer").
62 |
# File 'lib/inquirex/llm/node.rb', line 62 def collecting? = true |
#display? ⇒ Boolean
LLM verbs are never display-only.
65 |
# File 'lib/inquirex/llm/node.rb', line 65 def display? = false |
#llm_verb? ⇒ Boolean
Whether this is an LLM-powered step requiring server processing.
68 |
# File 'lib/inquirex/llm/node.rb', line 68 def llm_verb? = true |
#narrative? ⇒ Boolean
Whether this step closes the flow with prose for the user to read. Narrative steps carry no schema and no transitions.
72 |
# File 'lib/inquirex/llm/node.rb', line 72 def narrative? = NARRATIVE_VERBS.include?(@verb) |
#summarize? ⇒ Boolean
Whether this is the summarize step.
75 |
# File 'lib/inquirex/llm/node.rb', line 75 def summarize? = @verb == :summarize |
#to_h ⇒ Hash
Serializes to a plain Hash. LLM metadata is nested under "llm". Fallback procs are stripped (server-side only). All transitions are marked requires_server: true.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/inquirex/llm/node.rb', line 82 def to_h hash = { "verb" => @verb.to_s } hash["question"] = @question if @question hash["text"] = @text if @text hash["transitions"] = @transitions.map(&:to_h) unless @transitions.empty? hash["skip_if"] = @skip_if.to_h if @skip_if hash["requires_server"] = true llm_hash = { "prompt" => @prompt } llm_hash["schema"] = @schema.to_h if @schema llm_hash["from_steps"] = @from_steps.map(&:to_s) unless @from_steps.empty? llm_hash["from_all"] = true if @from_all llm_hash["model"] = @model.to_s if @model llm_hash["temperature"] = @temperature if @temperature llm_hash["max_tokens"] = @max_tokens if @max_tokens hash["llm"] = llm_hash hash end |