inquirex-llm 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 737239fae63381a1b7ab52afd9a811e6d57f4b781379c8dbaa694c7458e940a2
4
- data.tar.gz: 531baf672a53604111e9799bdb2a1af4bfac01933a304589da6c68655db57dfb
3
+ metadata.gz: db7e0d183259c896f2b1b5d9bd6ce1808f3a0c89de415a7129d2da2133a1ea49
4
+ data.tar.gz: dc3a1659f93182d83972dfccaaacbec820509823c293a97de4ac6df596409701
5
5
  SHA512:
6
- metadata.gz: 5a372a04fd14caddf249fd5b04d63f87508dc468ea50de016f57ac968814f4b8641fe96e6b05f05bc433a2c77978ffd120b4529a3ae5590867da46eb4e5f00e4
7
- data.tar.gz: f8b604991408d6b61a063a9f337e32fe8de96c0de5b6eda7bf08dfe496f19756833abae6c3437cf6078c9efe5ff5230c05f14f3b0ea2e205b68c9efc8d7bb416
6
+ metadata.gz: 6b8e8a8c3069ae95d904e3635d3b4947997e812d678dbf562040ae0647563692edef7364977e20ac2eb972d18d3aaeed88f606a1b05fc2d842ba201ff487b748
7
+ data.tar.gz: a3d8a7a89d4f22b7c3420ae1b22114895bee402314b6e4d303dfd3702f8afae1aeb87eac9a35e53090279a188b2d838c56759a8d2cecfc31179006f64d0d8fe7
data/README.md CHANGED
@@ -1,27 +1,31 @@
1
- [![Ruby](https://github.com/inquirex/inquirex-llm/actions/workflows/main.yml/badge.svg)](https://github.com/inquirex/inquirex-llm/actions/workflows/main.yml)  ![Coverage](docs/badges/coverage_badge.svg)
1
+ [![Gem Version](https://badge.fury.io/rb/inquirex-llm.svg)](https://badge.fury.io/rb/inquirex-llm) [![Ruby](https://github.com/inquirex/inquirex-llm/actions/workflows/main.yml/badge.svg)](https://github.com/inquirex/inquirex-llm/actions/workflows/main.yml) ![Coverage](docs/badges/coverage_badge.svg)
2
2
 
3
3
  # inquirex-llm
4
4
 
5
5
  LLM integration verbs for the [Inquirex](https://github.com/inquirex/inquirex) questionnaire engine.
6
6
 
7
- Extends the core DSL with four server-side verbs -- `clarify`, `describe`, `summarize`, and `detour` -- that bridge free-text answers and structured data via LLM processing. Ships with a pluggable adapter interface and a `NullAdapter` for testing.
7
+ Extends the core DSL with a server-side `extract` verb (alias: `clarify`) that turns free-text answers into structured data via LLM processing. Ships with a pluggable adapter interface and a `NullAdapter` for testing. (`describe`, `summarize`, and `detour` are temporarily parked.)
8
8
 
9
- ## Status
9
+ `inquirex` is a pure Ruby, declarative, rules-driven questionnaire engine for building conditional intake forms, qualification wizards, and branching surveys.
10
10
 
11
- - Version: `0.1.0`
12
- - Ruby: `>= 4.0.0`
13
- - Test suite: `111 examples, 0 failures`
14
- - Depends on: `inquirex` (core gem)
15
-
16
- ## Installation
17
-
18
- ```ruby
19
- gem "inquirex-llm"
20
- ```
11
+ > [!IMPORTANT]
12
+ >
13
+ > Note that `inquirex-llm` is part of an entire ecosystem that contains:
14
+ >
15
+ > - [`inquirex`](https://github.com/inquirex/inquirex)
16
+ > - [`inquirex-llm`](https://github.com/inquirex/inquirex-llm)
17
+ > - [`inquirex-tty`](https://github.com/inquirex/inquirex-)
18
+ > - [`inquirex-js`](https://github.com/inquirex/inquirex-js) (`npmjs` module [`@kigster/inquirex-js`](https://www.npmjs.com/package/@kigster/inquirex-js))
19
+ >
20
+ > For a presentation about these gems and what they do please watch the [RubySF presentation](https://www.youtube.com/watch?v=iaoKW7Ap3_M&t=1s) and you can also [view the slides form the presentation](https://reinvent.one/images/talks/pdfs/2026.inquirex.pdf).
21
+ >
22
+ > Finally, the SaaS application [qualified.at](https://qualified.at) allows users to leverage the ecosystem by creating their own custom lead intake flows and integrating them on their own sites.
21
23
 
22
24
  ## Usage
23
25
 
24
- `require "inquirex-llm"` injects the LLM verbs into the core `Inquirex.define` DSL. No separate entry point needed.
26
+ `require "inquirex-llm"` injects the LLM verbs into the core `Inquirex.define` DSL.
27
+
28
+ No separate entry point needed.
25
29
 
26
30
  ```ruby
27
31
  require "inquirex"
@@ -36,7 +40,7 @@ definition = Inquirex.define id: "tax-intake-2026", version: "1.0.0" do
36
40
  transition to: :extracted
37
41
  end
38
42
 
39
- clarify :extracted do
43
+ extract :extracted do
40
44
  from :description
41
45
  prompt "Extract structured business information from the description."
42
46
  schema industry: :string,
@@ -45,15 +49,9 @@ definition = Inquirex.define id: "tax-intake-2026", version: "1.0.0" do
45
49
  estimated_revenue: :currency
46
50
  model :claude_sonnet
47
51
  temperature 0.2
48
- transition to: :summary
49
- end
50
-
51
- summarize :summary do
52
- from_all
53
- prompt "Summarize this client's tax situation and flag complexity concerns."
54
52
  transition to: :done
55
53
  end
56
-
54
+
57
55
  say :done do
58
56
  text "Thank you! We'll be in touch."
59
57
  end
@@ -62,14 +60,14 @@ end
62
60
 
63
61
  All core verbs (`ask`, `say`, `header`, `btw`, `warning`, `confirm`) and widget hints work alongside LLM verbs in the same `Inquirex.define` block.
64
62
 
65
- ## LLM Verbs
63
+ ## Currently Supported LLM Verbs
66
64
 
67
- ### `clarify`
65
+ ### `extract` (alias: `clarify`)
68
66
 
69
- Extract structured data from a free-text answer. Requires `from`, `prompt`, and `schema`.
67
+ Extract structured data from a free-text answer. Requires `from` (or `from_all`), `prompt`, and `schema`. The stored/serialized verb is always `"extract"`; `clarify` is a DSL-only alias.
70
68
 
71
69
  ```ruby
72
- clarify :business_extracted do
70
+ extract :business_extracted do
73
71
  from :business_description
74
72
  prompt "Extract structured business information."
75
73
  schema industry: :string, employee_count: :integer, revenue: :currency
@@ -80,57 +78,20 @@ clarify :business_extracted do
80
78
  end
81
79
  ```
82
80
 
83
- ### `describe`
84
-
85
- Generate natural-language text from structured data. Requires `from` and `prompt`. No schema needed.
86
-
87
- ```ruby
88
- describe :business_narrative do
89
- from :business_extracted
90
- prompt "Write a brief narrative of this business for the intake report."
91
- transition to: :next_step
92
- end
93
- ```
94
-
95
- ### `summarize`
96
-
97
- Produce a summary of all or selected answers. Use `from_all` to pass everything, or `from` to select specific steps.
98
-
99
- ```ruby
100
- summarize :intake_summary do
101
- from_all
102
- prompt "Summarize this client's tax situation."
103
- transition to: :review
104
- end
105
- ```
106
-
107
- ### `detour`
108
-
109
- Dynamically generate follow-up questions based on an answer. The server adapter handles presenting the generated questions and collecting responses. Requires `from`, `prompt`, and `schema`.
110
-
111
- ```ruby
112
- detour :followup do
113
- from :description
114
- prompt "Generate 2-3 follow-up questions to clarify the tax situation."
115
- schema questions: :array, answers: :hash
116
- transition to: :next_step
117
- end
118
- ```
119
-
120
81
  ## DSL Methods (inside LLM verb blocks)
121
82
 
122
- | Method | Purpose | Required |
123
- |--------|---------|----------|
124
- | `prompt "..."` | LLM prompt template | Always |
125
- | `schema key: :type, ...` | Expected output structure | `clarify`, `detour` |
126
- | `from :step_id` | Source step(s) whose answers feed the LLM | `clarify`, `describe`, `detour` |
127
- | `from_all` | Pass all collected answers to the LLM | Alternative to `from` |
128
- | `model :claude_sonnet` | Optional model hint for the adapter | No |
129
- | `temperature 0.3` | Optional sampling temperature | No |
130
- | `max_tokens 1024` | Optional max output tokens | No |
131
- | `fallback { \|answers\| ... }` | Server-side fallback (stripped from JSON) | No |
132
- | `transition to: :step` | Conditional transition (same as core) | No |
133
- | `skip_if rule` | Skip step when condition is true | No |
83
+ | Method | Purpose | Required |
84
+ | ------------------------------ | ----------------------------------------- | ----------------------------- |
85
+ | `prompt "..."` | LLM prompt template | Always |
86
+ | `schema key: :type, ...` | Expected output structure | `extract` |
87
+ | `from :step_id` | Source step(s) whose answers feed the LLM | `extract` (or use `from_all`) |
88
+ | `from_all` | Pass all collected answers to the LLM | Alternative to `from` |
89
+ | `model :claude_sonnet` | Optional model hint for the adapter | No |
90
+ | `temperature 0.3` | Optional sampling temperature | No |
91
+ | `max_tokens 1024` | Optional max output tokens | No |
92
+ | `fallback { \|answers\| ... }` | Server-side fallback (stripped from JSON) | No |
93
+ | `transition to: :step` | Conditional transition (same as core) | No |
94
+ | `skip_if rule` | Skip step when condition is true | No |
134
95
 
135
96
  ## Engine Integration
136
97
 
@@ -161,16 +122,14 @@ result = adapter.call(engine.current_step)
161
122
 
162
123
  ## Built-in Adapters
163
124
 
164
- | Class | Provider | API | Auth | Key env var |
165
- |------------------------------------|-----------|---------------------------------------|-----------------------------|-----------------------|
166
- | `Inquirex::LLM::NullAdapter` | — | none (placeholders) | none | — |
167
- | `Inquirex::LLM::AnthropicAdapter` | Anthropic | `/v1/messages` | `x-api-key` header | `ANTHROPIC_API_KEY` |
168
- | `Inquirex::LLM::OpenAIAdapter` | OpenAI | `/v1/chat/completions` (JSON mode) | `Authorization: Bearer …` | `OPENAI_API_KEY` |
125
+ | Class | Provider | API | Auth | Key env var |
126
+ | --------------------------------------- | --------- | ---------------------------------- | ------------------------- | ------------------- |
127
+ | `Inquirex::LLM::NullAdapter` | — | none (placeholders) | none | — |
128
+ | `Inquirex::LLM::AnthropicAdapter` | Anthropic | `/v1/messages` | `x-api-key` header | `ANTHROPIC_API_KEY` |
129
+ | `Inquirex::LLM::OpenAIAdapter` | OpenAI | `/v1/chat/completions` (JSON mode) | `Authorization: Bearer …` | `OPENAI_API_KEY` |
130
+ | `Inquirex::LLM::LittleLLMAdapter` (TBD) | Any | OpenAI Compatible API | OpenAI Compatible Auth | Provider Specific |
169
131
 
170
- Both real adapters use `net/http` (stdlib, no extra dependency), inject the
171
- declared `schema` into the system prompt as a strict JSON contract, and raise
172
- `Inquirex::LLM::Errors::AdapterError` on HTTP / parse failures and
173
- `SchemaViolationError` when the model's output is missing declared fields.
132
+ The Anthropic and OpenAI adapters use `net/http` (stdlib, no extra dependency), inject the declared `schema` into the system prompt as a strict JSON contract, and raise `Inquirex::LLM::Errors::AdapterError` on HTTP / parse failures and `SchemaViolationError` when the model's output is missing declared fields.
174
133
 
175
134
  ### AnthropicAdapter
176
135
 
@@ -181,8 +140,7 @@ adapter = Inquirex::LLM::AnthropicAdapter.new(
181
140
  )
182
141
  ```
183
142
 
184
- Recognized `model :symbol` values in the DSL: `:claude_sonnet`,
185
- `:claude_haiku`, `:claude_opus` (mapped to the current concrete model ids).
143
+ Recognized `model :symbol` values in the DSL: `:claude_sonnet`, `:claude_haiku`, `:claude_opus` (mapped to the current concrete model ids).
186
144
 
187
145
  ### OpenAIAdapter
188
146
 
@@ -193,18 +151,11 @@ adapter = Inquirex::LLM::OpenAIAdapter.new(
193
151
  )
194
152
  ```
195
153
 
196
- Uses Chat Completions with `response_format: { type: "json_object" }` so the
197
- model is constrained to return valid JSON. Recognized DSL symbols: `:gpt_4o`,
198
- `:gpt_4o_mini`, `:gpt_4_1`, `:gpt_4_1_mini`. For cross-provider portability,
199
- the adapter also accepts the Claude symbols (`:claude_sonnet` → `gpt-4o` etc.)
200
- so a flow file that says `model :claude_sonnet` runs unchanged against either
201
- provider.
154
+ Uses Chat Completions with `response_format: { type: "json_object" }` so the model is constrained to return valid JSON. Recognized DSL symbols: `:gpt_4o`, `:gpt_4o_mini`, `:gpt_4_1`, `:gpt_4_1_mini`. For cross-provider portability, the adapter also accepts the Claude symbols (`:claude_sonnet` → `gpt-4o` etc.) so a flow file that says `model :claude_sonnet` runs unchanged against either provider.
202
155
 
203
156
  ## LLM-assisted Pre-fill Pattern
204
157
 
205
- A common use case: ask *one* open-ended question, let the LLM extract answers
206
- for *many* downstream questions, and only prompt the user for what the LLM
207
- couldn't determine. This is what the core engine's `Engine#prefill!` is for:
158
+ A common use case: ask *one* open-ended question, let the LLM extract answers for *many* downstream questions, and only prompt the user for what the LLM couldn't determine. This is what the core engine's `Engine#prefill!` is for:
208
159
 
209
160
  ```ruby
210
161
  definition = Inquirex.define id: "tax-intake" do
@@ -216,7 +167,7 @@ definition = Inquirex.define id: "tax-intake" do
216
167
  transition to: :extracted
217
168
  end
218
169
 
219
- clarify :extracted do
170
+ extract :extracted do
220
171
  from :describe
221
172
  prompt "Extract: filing_status, dependents, income_types, state_filing."
222
173
  schema filing_status: :string,
@@ -241,7 +192,7 @@ definition = Inquirex.define id: "tax-intake" do
241
192
  skip_if not_empty(:dependents)
242
193
  transition to: :income_types
243
194
  end
244
- # …and so on for every field in the clarify schema
195
+ # …and so on for every field in the extract schema
245
196
  end
246
197
 
247
198
  engine = Inquirex::Engine.new(definition)
@@ -257,12 +208,7 @@ engine.prefill!(result) # splats into top-level answers
257
208
  # whichever field the LLM couldn't fill in.
258
209
  ```
259
210
 
260
- `Engine#prefill!` is non-destructive (won't clobber an answer the user already
261
- gave), ignores `nil`/empty values so they don't spuriously trigger
262
- `not_empty`, and auto-advances past any step whose `skip_if` now evaluates
263
- true. See [examples/09_tax_preparer_llm.rb](../inquirex-tty/examples/09_tax_preparer_llm.rb)
264
- for a complete runnable flow, or the repo-level `demo_llm_intake.rb` for a
265
- scripted end-to-end walkthrough.
211
+ `Engine#prefill!` is non-destructive (won't clobber an answer the user already gave), ignores `nil`/empty values so they don't spuriously trigger `not_empty`, and auto-advances past any step whose `skip_if` now evaluates true. See [examples/09_tax_preparer_llm.rb](../inquirex-tty/examples/09_tax_preparer_llm.rb) for a complete runnable flow, or the repo-level `demo_llm_intake.rb` for a scripted end-to-end walkthrough.
266
212
 
267
213
  ## JSON Serialization
268
214
 
@@ -270,9 +216,9 @@ LLM steps serialize with `"requires_server": true` so the JS widget knows to rou
270
216
 
271
217
  ```json
272
218
  {
273
- "verb": "clarify",
219
+ "verb": "extract",
274
220
  "requires_server": true,
275
- "transitions": [{ "to": "summary", "requires_server": true }],
221
+ "transitions": [{ "to": "next_step", "requires_server": true }],
276
222
  "llm": {
277
223
  "prompt": "Extract structured business information.",
278
224
  "schema": {
@@ -313,6 +259,47 @@ end
313
259
 
314
260
  The base class provides `#source_answers` (gathers relevant answers) and `#validate_output!` (checks schema conformance).
315
261
 
262
+ ## Future Possible LLM Verbs
263
+
264
+ ### `describe`
265
+
266
+ Generate natural-language text from structured data. Requires `from` and `prompt`. No schema needed.
267
+
268
+ ```ruby
269
+ describe :business_narrative do
270
+ from :business_extracted
271
+ prompt "Write a brief narrative of this business for the intake report."
272
+ transition to: :next_step
273
+ end
274
+ ```
275
+
276
+ ### `summarize`
277
+
278
+ Produce a summary of all or selected answers. Use `from_all` to pass everything, or `from` to select specific steps.
279
+
280
+ ```ruby
281
+ summarize :intake_summary do
282
+ from_all
283
+ prompt "Summarize this client's tax situation."
284
+ transition to: :review
285
+ end
286
+ ```
287
+
288
+ ### `detour` (parked)
289
+
290
+ Dynamically generate follow-up questions based on an answer. The server adapter handles presenting the generated questions and collecting responses. Requires `from`, `prompt`, and `schema`.
291
+
292
+ ```ruby
293
+ detour :followup do
294
+ from :description
295
+ prompt "Generate 2-3 follow-up questions to clarify the tax situation."
296
+ schema questions: :array, answers: :hash
297
+ transition to: :next_step
298
+ end
299
+ ```
300
+
301
+ ##
302
+
316
303
  ## Development
317
304
 
318
305
  ```bash
@@ -323,4 +310,8 @@ bundle exec rubocop
323
310
 
324
311
  ## License
325
312
 
326
- MIT. See [LICENSE.txt](LICENSE.txt).
313
+ © 2026 Konstantin Gredeskoul.
314
+
315
+ Distributed under the MIT License.
316
+
317
+ See [LICENSE.txt](LICENSE.txt) for details.
@@ -15,7 +15,7 @@
15
15
  <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
16
16
  <text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
17
17
  <text x="31.5" y="14">coverage</text>
18
- <text x="80" y="15" fill="#010101" fill-opacity=".3">99%</text>
19
- <text x="80" y="14">99%</text>
18
+ <text x="80" y="15" fill="#010101" fill-opacity=".3">98%</text>
19
+ <text x="80" y="14">98%</text>
20
20
  </g>
21
21
  </svg>
data/justfile CHANGED
@@ -1,38 +1,69 @@
1
- set shell := ["bash", "-lc"]
1
+ # Tell 'just' to run bash, source our setup script, then execute the recipe
2
+ set shell := ["bash", "-c"]
2
3
 
3
- rbenv := 'eval "$(rbenv init -)"'
4
+ version := `grep VERSION lib/inquirex/llm/version.rb | awk '{print $3}' | tr -d '"' | tr -d '\n'`
5
+ rbenv := 'eval "$(rbenv init bash)"; bundle exec '
6
+ repo := 'git@github.com:inquirex/inquirex.git'
4
7
 
5
8
  [no-exit-message]
6
9
  recipes:
7
- @just --choose
10
+ just --choose
8
11
 
9
12
  # Sync all dependencies
10
13
  install:
11
- {{rbenv}} && bin/setup
14
+ bin/setup
12
15
 
13
- # Lint and reformat files
14
- lint-fix *args:
15
- {{rbenv}} && bundle exec rubocop -a
16
-
17
- alias format := lint-fix
16
+ build: install
18
17
 
19
18
  # Lint and reformat files
20
19
  lint:
21
- {{rbenv}} && bundle exec rubocop
20
+ {{ rbenv }} rubocop
21
+
22
+ # Lint and reformat files (-a) — pass -A as an argument
23
+ format *args:
24
+ {{ rbenv }} rubocop -a {{ args }}
22
25
 
23
26
  # Run all the tests
24
27
  test *args:
25
- {{rbenv}} && ENVIRONMENT=test bundle exec rspec {{args}}
28
+ export ENVIRONMENT=test; {{ rbenv }} rspec {{args}}
26
29
 
27
30
  # Run tests with coverage
28
31
  test-coverage *args:
29
- ENVIRONMENT=test COVERAGE=true bundle exec rspec
32
+ export ENVIRONMENT=test; export COVERAGE=true; {{ rbenv }} rspec {{ args }}
33
+
34
+ check-all: install lint test-coverage
30
35
 
31
36
  clean:
32
37
  #!/usr/bin/env bash
33
- find . -name .DS_Store -delete -print || true
34
- rm -rf tmp/*
38
+ @find . -name .DS_Store -delete -print || true
39
+ @rm -rf tmp/*
35
40
 
36
41
  # Run all lefthook pre-commit hooks
37
- ci:
38
- {{rbenv}} && lefthook run pre-commit --all-files
42
+ lefthook:
43
+ {{ rbenv }} lefthook run pre-commit --all-files
44
+
45
+ # Print current gem version
46
+ version:
47
+ @echo "{{ version }}"
48
+
49
+ # Clobber
50
+ clobber:
51
+ {{ rbenv }} rake clobber
52
+
53
+ # Generate documentation
54
+ doc:
55
+ #!/usr/bin/env bash
56
+ {{ rbenv }} rake doc
57
+
58
+ # Create
59
+ publish: build
60
+ {{ rbenv }} rake release[remote]
61
+
62
+
63
+ # Tag v{{ version }}, publish the GH release, & refresh the Homebrew tap.
64
+ release:
65
+ git fetch --tags
66
+ git tag -f "v{{ version }}"
67
+ git push -f --tags
68
+ gh release delete -y "v{{ version }}" --repo {{ repo }} 2>/dev/null || true
69
+ gh release create "v{{ version }}" --generate-notes --repo {{ repo }}
@@ -28,7 +28,7 @@ module Inquirex
28
28
  #
29
29
  # @param node [LLM::Node] the LLM step to process
30
30
  # @param answers [Hash] current collected answers
31
- # @return [Hash, String] structured output (for clarify/detour) or text (for describe/summarize)
31
+ # @return [Hash, String] structured output (for extract) or text (when no schema)
32
32
  # @raise [Errors::AdapterError] if the LLM call fails
33
33
  # @raise [Errors::SchemaViolationError] if output doesn't match schema
34
34
  def call(node, answers)
@@ -5,7 +5,7 @@ module Inquirex
5
5
  module DSL
6
6
  # Mixin that adds LLM verb methods to Inquirex::DSL::FlowBuilder.
7
7
  # Included automatically when `require "inquirex-llm"` is called,
8
- # so that `Inquirex.define` gains clarify/describe/summarize/detour
8
+ # so that `Inquirex.define` gains `extract` (and its `clarify` alias)
9
9
  # without needing a separate entry point.
10
10
  #
11
11
  # All core verbs (ask, say, header, btw, warning, confirm) remain
@@ -15,34 +15,36 @@ module Inquirex
15
15
  # structured data matching the declared schema.
16
16
  #
17
17
  # @param id [Symbol] step id
18
- def clarify(id, &)
19
- add_llm_step(id, :clarify, &)
18
+ def extract(id, &)
19
+ add_llm_step(id, :extract, &)
20
20
  end
21
21
 
22
- # Defines an LLM description step: takes structured data and produces
23
- # natural-language text.
24
- #
25
- # @param id [Symbol] step id
26
- def describe(id, &)
27
- add_llm_step(id, :describe, &)
28
- end
22
+ alias clarify extract
29
23
 
30
- # Defines an LLM summarization step: takes all or selected answers and
31
- # produces a textual summary.
32
- #
33
- # @param id [Symbol] step id
34
- def summarize(id, &)
35
- add_llm_step(id, :summarize, &)
36
- end
24
+ # # Defines an LLM description step: takes structured data and produces
25
+ # # natural-language text.
26
+ # #
27
+ # # @param id [Symbol] step id
28
+ # def describe(id, &)
29
+ # add_llm_step(id, :describe, &)
30
+ # end
37
31
 
38
- # Defines an LLM detour step: based on an answer, dynamically generates
39
- # follow-up questions. The server adapter handles presenting the generated
40
- # questions and collecting responses.
41
- #
42
- # @param id [Symbol] step id
43
- def detour(id, &)
44
- add_llm_step(id, :detour, &)
45
- end
32
+ # # Defines an LLM summarization step: takes all or selected answers and
33
+ # # produces a textual summary.
34
+ # #
35
+ # # @param id [Symbol] step id
36
+ # def summarize(id, &)
37
+ # add_llm_step(id, :summarize, &)
38
+ # end
39
+
40
+ # # Defines an LLM detour step: based on an answer, dynamically generates
41
+ # # follow-up questions. The server adapter handles presenting the generated
42
+ # # questions and collecting responses.
43
+ # #
44
+ # # @param id [Symbol] step id
45
+ # def detour(id, &)
46
+ # add_llm_step(id, :detour, &)
47
+ # end
46
48
 
47
49
  private
48
50
 
@@ -8,7 +8,7 @@ module Inquirex
8
8
  # while inheriting transition and skip_if from the core StepBuilder.
9
9
  #
10
10
  # @example
11
- # clarify :business_extracted do
11
+ # extract :business_extracted do
12
12
  # from :business_description
13
13
  # prompt "Extract structured business info."
14
14
  # schema industry: :string, employee_count: :integer
@@ -112,7 +112,7 @@ module Inquirex
112
112
  @skip_if = rule
113
113
  end
114
114
 
115
- # Optional display text (used by describe/summarize for user-visible labels).
115
+ # Optional display text (user-visible label for the step).
116
116
  #
117
117
  # @param content [String]
118
118
  def question(content)
@@ -159,15 +159,20 @@ module Inquirex
159
159
  def validate!(id)
160
160
  raise Errors::DefinitionError, "LLM step #{id.inspect} requires a prompt" if @prompt.nil?
161
161
 
162
- if %i[clarify detour].include?(@verb) && @schema_fields.empty?
162
+ # Schema required for extract (and formerly detour).
163
+ if %i[extract].include?(@verb) && @schema_fields.empty?
164
+ # if %i[extract detour].include?(@verb) && @schema_fields.empty?
163
165
  raise Errors::DefinitionError,
164
166
  "LLM step #{id.inspect} (#{@verb}) requires a schema"
165
167
  end
166
168
 
167
- return unless @from_steps.empty? && !@from_all && @verb != :summarize
169
+ return unless @from_steps.empty? && !@from_all
170
+ # return unless @from_steps.empty? && !@from_all && @verb != :summarize
168
171
 
169
- # clarify/describe/detour should reference source steps or from_all
170
- return unless %i[clarify describe detour].include?(@verb)
172
+ # extract (and formerly describe/detour) should reference source steps or from_all
173
+ return unless %i[extract].include?(@verb)
174
+
175
+ # return unless %i[extract describe detour].include?(@verb)
171
176
 
172
177
  raise Errors::DefinitionError,
173
178
  "LLM step #{id.inspect} (#{@verb}) requires `from` or `from_all`"
@@ -7,16 +7,16 @@ module Inquirex
7
7
  # step references, and model configuration.
8
8
  #
9
9
  # LLM verbs:
10
- # :clarify — extract structured data from a free-text answer
11
- # :describe — generate natural-language text from structured data
12
- # :summarize — produce a summary of all or selected answers
13
- # :detour — dynamically generate follow-up questions based on an answer
10
+ # :extract — extract structured data from a free-text answer
11
+ # # :describe — generate natural-language text from structured data
12
+ # # :summarize — produce a summary of all or selected answers
13
+ # # :detour — dynamically generate follow-up questions based on an answer
14
14
  #
15
15
  # All LLM nodes are collecting (they produce answers) and require server
16
16
  # round-trips. The frontend shows a "thinking" state while the server processes.
17
17
  #
18
18
  # @attr_reader prompt [String] LLM prompt template
19
- # @attr_reader schema [Schema, nil] expected output structure (required for clarify/detour)
19
+ # @attr_reader schema [Schema, nil] expected output structure (required for extract)
20
20
  # @attr_reader from_steps [Array<Symbol>] source step ids whose answers feed the LLM
21
21
  # @attr_reader from_all [Boolean] whether to pass all collected answers to the LLM
22
22
  # @attr_reader model [Symbol, nil] optional model hint (e.g. :claude_sonnet)
@@ -24,7 +24,8 @@ module Inquirex
24
24
  # @attr_reader max_tokens [Integer, nil] optional max output tokens
25
25
  # @attr_reader fallback [Proc, nil] server-side fallback (stripped from JSON)
26
26
  class Node < Inquirex::Node
27
- LLM_VERBS = %i[clarify describe summarize detour].freeze
27
+ LLM_VERBS = %i[extract].freeze
28
+ # LLM_VERBS = %i[extract describe summarize detour].freeze
28
29
 
29
30
  attr_reader :prompt,
30
31
  :schema,
@@ -89,7 +90,8 @@ module Inquirex
89
90
  # @param hash [Hash]
90
91
  # @return [LLM::Node]
91
92
  def self.from_h(id, hash)
92
- verb = hash["verb"] || hash[:verb]
93
+ verb = hash["verb"] || hash[:verb]
94
+ verb = :extract if verb.to_s == "clarify" # DSL alias; normalize wire format
93
95
  question = hash["question"] || hash[:question]
94
96
  text = hash["text"] || hash[:text]
95
97
  transitions_data = hash["transitions"] || hash[:transitions] || []
@@ -5,13 +5,13 @@ module Inquirex
5
5
  # Test adapter that returns schema-conformant placeholder values without
6
6
  # calling any LLM API. Useful for testing flows that include LLM steps.
7
7
  #
8
- # For clarify/detour steps with a schema, returns a hash of default values
9
- # matching each field's declared type. For describe/summarize steps, returns
8
+ # For extract steps with a schema, returns a hash of default values
9
+ # matching each field's declared type. Without a schema, returns
10
10
  # a placeholder string.
11
11
  #
12
12
  # @example
13
13
  # adapter = Inquirex::LLM::NullAdapter.new
14
- # result = adapter.call(clarify_node, answers)
14
+ # result = adapter.call(extract_node, answers)
15
15
  # # => { industry: "", employee_count: 0, ... }
16
16
  class NullAdapter < Adapter
17
17
  TYPE_DEFAULTS = {
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Inquirex
4
4
  module LLM
5
- VERSION = "0.3.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
data/lib/inquirex/llm.rb CHANGED
@@ -17,11 +17,11 @@ require_relative "llm/dsl/flow_builder"
17
17
  module Inquirex
18
18
  # LLM integration layer for Inquirex flows.
19
19
  #
20
- # Extends the core DSL with four LLM-powered verbs that run server-side:
21
- # - clarify — extract structured data from free-text answers
22
- # - describe — generate natural-language text from structured data
23
- # - summarize — produce a summary of all or selected answers
24
- # - detour — dynamically generate follow-up questions
20
+ # Extends the core DSL with LLM-powered verbs that run server-side:
21
+ # - extract — extract structured data from free-text answers (`clarify` is an alias)
22
+ # # - describe — generate natural-language text from structured data
23
+ # # - summarize — produce a summary of all or selected answers
24
+ # # - detour — dynamically generate follow-up questions
25
25
  #
26
26
  # LLM calls never happen on the frontend. Steps are marked `requires_server: true`
27
27
  # in the JSON wire format so the JS widget knows to round-trip to the server.
@@ -33,7 +33,7 @@ module Inquirex
33
33
  # Inquirex.define id: "intake" do
34
34
  # start :description
35
35
  # ask(:description) { type :text; question "Describe your business."; transition to: :extracted }
36
- # clarify(:extracted) { from :description; prompt "Extract info."; schema name: :string; transition to: :done }
36
+ # extract(:extracted) { from :description; prompt "Extract info."; schema name: :string; transition to: :done }
37
37
  # say(:done) { text "Done!" }
38
38
  # end
39
39
  module LLM
@@ -41,5 +41,5 @@ module Inquirex
41
41
  end
42
42
 
43
43
  # Inject LLM verbs into the core FlowBuilder so that Inquirex.define
44
- # gains clarify/describe/summarize/detour when this gem is loaded.
44
+ # gains extract (and clarify as an alias) when this gem is loaded.
45
45
  Inquirex::DSL::FlowBuilder.include(Inquirex::LLM::DSL::FlowBuilderExtension)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inquirex-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -15,18 +15,18 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.2'
18
+ version: '0.4'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.2'
26
- description: Extends the Inquirex DSL with four LLM-powered verbs clarify, describe,
27
- summarize, and detour — that run server-side to extract structured data, generate
28
- text, and dynamically branch flows. Pluggable adapter interface keeps the gem LLM-agnostic;
29
- a NullAdapter ships for testing.
25
+ version: '0.4'
26
+ description: 'Extends the Inquirex DSL with an LLM-powered `extract` verb (alias:
27
+ `clarify`) that runs server-side to turn free-text answers into structured data.
28
+ Pluggable adapter interface keeps the gem LLM-agnostic; a NullAdapter ships for
29
+ testing.'
30
30
  email:
31
31
  - kigster@gmail.com
32
32
  executables:
@@ -81,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  requirements: []
84
- rubygems_version: 4.0.10
84
+ rubygems_version: 4.0.16
85
85
  specification_version: 4
86
86
  summary: LLM integration verbs for the Inquirex questionnaire engine
87
87
  test_files: []