Class: Inquirex::LLM::Adapter
- Inherits:
-
Object
- Object
- Inquirex::LLM::Adapter
- Defined in:
- lib/inquirex/llm/adapter.rb
Overview
Abstract interface for LLM adapters. Adapters bridge the gap between LLM::Node definitions and actual LLM API calls.
Implementations must:
1. Accept an LLM::Node and current answers
2. Construct the appropriate prompt (using node.prompt, node.from_steps, etc.)
3. Call the LLM API
4. Parse and validate the response against node.schema (if present)
5. Return a Hash or String result
The adapter is invoked server-side when the engine reaches an LLM step. It is never called on the frontend.
Direct Known Subclasses
Instance Method Summary collapse
-
#call(node, answers) ⇒ Hash, String
Processes an LLM step and returns the result.
-
#normalize_output(node, output) ⇒ Hash, Object
Canonicalizes LLM output against the schema's value constraints — the regression guard for "the model answered with a label or a case variant".
-
#source_answers(node, answers) ⇒ Hash
Gathers the source answer data that feeds the LLM prompt.
-
#summarize(node, transcript, answers = {}) ⇒ String
Produces the closing prose summary for a
summarizestep. -
#summary_input(transcript) ⇒ String
The user-side prompt for a summarize call: the transcript, and nothing else that could compete with it for the model's attention.
-
#validate_output!(node, output) ⇒ Object
Validates adapter output against the node's schema.
Instance Method Details
#call(node, answers) ⇒ Hash, String
Processes an LLM step and returns the result.
34 35 36 |
# File 'lib/inquirex/llm/adapter.rb', line 34 def call(node, answers) raise NotImplementedError, "#{self.class}#call must be implemented" end |
#normalize_output(node, output) ⇒ Hash, Object
Canonicalizes LLM output against the schema's value constraints — the regression guard for "the model answered with a label or a case variant". Every value-constrained field is matched against the allowed form values: an exact match passes through, a case-insensitive match is rewritten to the canonical value, and a value outside the list becomes nil (enum) or is dropped from the array (multi_enum) — "unknown, will ask" instead of junk that prefills the wrong option downstream. Unconstrained fields are untouched.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/inquirex/llm/adapter.rb', line 113 def normalize_output(node, output) schema = node.respond_to?(:schema) ? node.schema : nil return output unless schema && output.is_a?(Hash) output.to_h do |key, raw| values = schema.values_for(key) next [key, raw] unless values if schema.fields[key.to_sym] == :multi_enum [key, Array(raw).filter_map { |entry| canonical_value(values, entry) }] else [key, canonical_value(values, raw)] end end end |
#source_answers(node, answers) ⇒ Hash
Gathers the source answer data that feeds the LLM prompt.
75 76 77 78 79 80 81 82 83 |
# File 'lib/inquirex/llm/adapter.rb', line 75 def source_answers(node, answers) if node.from_all answers.dup else node.from_steps.each_with_object({}) do |step_id, acc| acc[step_id] = answers[step_id] if answers.key?(step_id) end end end |
#summarize(node, transcript, answers = {}) ⇒ String
Produces the closing prose summary for a summarize step.
Separate from #call because nothing about it is the same: the prompt
is the gem's rather than the node's, the input is the session
transcript rather than selected answers, and the result is markdown
rather than a schema-shaped Hash. Keeping it apart also means an
existing custom adapter that only implements #call keeps working for
extract and fails loudly — rather than subtly — if a flow starts
asking it to summarise.
53 54 55 |
# File 'lib/inquirex/llm/adapter.rb', line 53 def summarize(node, transcript, answers = {}) raise NotImplementedError, "#{self.class}#summarize must be implemented" end |
#summary_input(transcript) ⇒ String
The user-side prompt for a summarize call: the transcript, and nothing else that could compete with it for the model's attention.
63 64 65 66 67 68 |
# File 'lib/inquirex/llm/adapter.rb', line 63 def summary_input(transcript) text = transcript.to_s.strip raise Errors::AdapterError, "Cannot summarize an empty transcript" if text.empty? "Here is the session transcript.\n\n#{text}" end |
#validate_output!(node, output) ⇒ Object
Validates adapter output against the node's schema.
90 91 92 93 94 95 96 97 98 |
# File 'lib/inquirex/llm/adapter.rb', line 90 def validate_output!(node, output) return unless node.schema missing = node.schema.missing_fields(output) return if missing.empty? raise Errors::SchemaViolationError, "LLM output for #{node.id.inspect} missing fields: #{missing.join(", ")}" end |