inquirex-llm 0.1.0 → 0.3.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: 10d488773dcdef0cbf1f5fe93cdb916b7bf2bdf585e45d283b98fcda77dcd68d
4
- data.tar.gz: 8c8128942112c74522f8f8c4e3be86904eeb5be510400f9c104f908ec24c7d5e
3
+ metadata.gz: 737239fae63381a1b7ab52afd9a811e6d57f4b781379c8dbaa694c7458e940a2
4
+ data.tar.gz: 531baf672a53604111e9799bdb2a1af4bfac01933a304589da6c68655db57dfb
5
5
  SHA512:
6
- metadata.gz: a2ec2170b2fd8dd56bbfaa05ad3dd9507c6880ddce90993caa2af9519ccc002a869d87e0c784f855272480ffb1500f545152daeff0d5f29dcb9d9b68ffdb3849
7
- data.tar.gz: 4f53ae8500c6cb8f11d89952f4b101a6fd919e3902e3d8bfed2b06fb0cb8b899b1fe93dfe309fddfb8e4c523b2b10d60d96551b9a2084efef09f118edaef197b
6
+ metadata.gz: 5a372a04fd14caddf249fd5b04d63f87508dc468ea50de016f57ac968814f4b8641fe96e6b05f05bc433a2c77978ffd120b4529a3ae5590867da46eb4e5f00e4
7
+ data.tar.gz: f8b604991408d6b61a063a9f337e32fe8de96c0de5b6eda7bf08dfe496f19756833abae6c3437cf6078c9efe5ff5230c05f14f3b0ea2e205b68c9efc8d7bb416
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,28 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-04-14 23:51:42 UTC using RuboCop version 1.86.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
11
+ # SupportedStyles: snake_case, normalcase, non_integer
12
+ # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
13
+ Naming/VariableNumber:
14
+ Exclude:
15
+ - 'lib/inquirex/llm/openai_adapter.rb'
16
+
17
+ # Offense count: 2
18
+ # Configuration parameters: AllowSubject.
19
+ RSpec/MultipleMemoizedHelpers:
20
+ Max: 6
21
+
22
+ # Offense count: 1
23
+ # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
24
+ # SupportedInflectors: default, active_support
25
+ RSpec/SpecFilePathFormat:
26
+ Exclude:
27
+ - '**/spec/routing/**/*'
28
+ - 'spec/inquirex/llm/openai_adapter_spec.rb'
data/README.md CHANGED
@@ -159,6 +159,111 @@ result = adapter.call(engine.current_step)
159
159
  # => { industry: "", employee_count: 0, revenue: 0.0 }
160
160
  ```
161
161
 
162
+ ## Built-in Adapters
163
+
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` |
169
+
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.
174
+
175
+ ### AnthropicAdapter
176
+
177
+ ```ruby
178
+ adapter = Inquirex::LLM::AnthropicAdapter.new(
179
+ api_key: ENV["ANTHROPIC_API_KEY"],
180
+ model: "claude-sonnet-4-20250514" # or pass the short symbol in the DSL
181
+ )
182
+ ```
183
+
184
+ Recognized `model :symbol` values in the DSL: `:claude_sonnet`,
185
+ `:claude_haiku`, `:claude_opus` (mapped to the current concrete model ids).
186
+
187
+ ### OpenAIAdapter
188
+
189
+ ```ruby
190
+ adapter = Inquirex::LLM::OpenAIAdapter.new(
191
+ api_key: ENV["OPENAI_API_KEY"],
192
+ model: "gpt-4o-mini"
193
+ )
194
+ ```
195
+
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.
202
+
203
+ ## LLM-assisted Pre-fill Pattern
204
+
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:
208
+
209
+ ```ruby
210
+ definition = Inquirex.define id: "tax-intake" do
211
+ start :describe
212
+
213
+ ask :describe do
214
+ type :text
215
+ question "Describe your 2025 tax situation."
216
+ transition to: :extracted
217
+ end
218
+
219
+ clarify :extracted do
220
+ from :describe
221
+ prompt "Extract: filing_status, dependents, income_types, state_filing."
222
+ schema filing_status: :string,
223
+ dependents: :integer,
224
+ income_types: :multi_enum,
225
+ state_filing: :string
226
+ model :claude_sonnet
227
+ transition to: :filing_status
228
+ end
229
+
230
+ ask :filing_status do
231
+ type :enum
232
+ question "Filing status?"
233
+ options %w[single married_filing_jointly head_of_household]
234
+ skip_if not_empty(:filing_status) # ← the whole trick
235
+ transition to: :dependents
236
+ end
237
+
238
+ ask :dependents do
239
+ type :integer
240
+ question "How many dependents?"
241
+ skip_if not_empty(:dependents)
242
+ transition to: :income_types
243
+ end
244
+ # …and so on for every field in the clarify schema
245
+ end
246
+
247
+ engine = Inquirex::Engine.new(definition)
248
+ adapter = Inquirex::LLM::OpenAIAdapter.new # or AnthropicAdapter
249
+
250
+ engine.answer("I'm MFJ with two kids in California, W-2 plus some crypto.")
251
+ result = adapter.call(engine.current_step, engine.answers)
252
+ engine.answer(result) # stored under :extracted
253
+ engine.prefill!(result) # splats into top-level answers
254
+
255
+ # Every downstream step whose skip_if rule now evaluates true gets
256
+ # auto-skipped by the engine. engine.current_step_id jumps straight to
257
+ # whichever field the LLM couldn't fill in.
258
+ ```
259
+
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.
266
+
162
267
  ## JSON Serialization
163
268
 
164
269
  LLM steps serialize with `"requires_server": true` so the JS widget knows to round-trip to the server. LLM metadata lives under an `"llm"` key:
@@ -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">98%</text>
19
- <text x="80" y="14">98%</text>
18
+ <text x="80" y="15" fill="#010101" fill-opacity=".3">99%</text>
19
+ <text x="80" y="14">99%</text>
20
20
  </g>
21
21
  </svg>
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "inquirex/llm/adapter"
7
+
8
+ module Inquirex
9
+ module LLM
10
+ # Real Anthropic Claude adapter for inquirex-llm.
11
+ #
12
+ # Usage:
13
+ # adapter = Inquirex::LLM::AnthropicAdapter.new(
14
+ # api_key: ENV["ANTHROPIC_API_KEY"],
15
+ # model: "claude-sonnet-4-20250514"
16
+ # )
17
+ # result = adapter.call(engine.current_step, engine.answers)
18
+ #
19
+ # The adapter:
20
+ # 1. Gathers source answers from the step's `from` / `from_all` declaration
21
+ # 2. Builds a prompt that includes the schema as a JSON contract
22
+ # 3. Calls the Anthropic Messages API
23
+ # 4. Parses the JSON response
24
+ # 5. Validates output against the declared schema
25
+ # 6. Returns the structured hash
26
+ class AnthropicAdapter < Adapter
27
+ API_URL = "https://api.anthropic.com/v1/messages"
28
+ API_VERSION = "2023-06-01"
29
+ DEFAULT_MODEL = "claude-sonnet-4-20250514"
30
+ DEFAULT_MAX_TOKENS = 2048
31
+
32
+ # Maps Inquirex short model symbols to concrete Anthropic model ids.
33
+ MODEL_MAP = {
34
+ claude_sonnet: "claude-sonnet-4-20250514",
35
+ claude_haiku: "claude-haiku-4-5-20251001",
36
+ claude_opus: "claude-opus-4-20250514"
37
+ }.freeze
38
+
39
+ # @param api_key [String, nil] defaults to ENV["ANTHROPIC_API_KEY"]
40
+ # @param model [String, nil] default model id when a node does not specify one
41
+ def initialize(api_key: nil, model: nil)
42
+ super()
43
+ @api_key = api_key || ENV.fetch("ANTHROPIC_API_KEY") {
44
+ raise ArgumentError, "ANTHROPIC_API_KEY is required (pass api_key: or set the env var)"
45
+ }
46
+ @default_model = model || DEFAULT_MODEL
47
+ end
48
+
49
+ # @param node [Inquirex::LLM::Node] the current LLM step
50
+ # @param answers [Hash] all collected answers so far
51
+ # @return [Hash] structured data matching the node's schema
52
+ # @raise [Errors::AdapterError] on API / parse failures
53
+ # @raise [Errors::SchemaViolationError] when the LLM output misses schema fields
54
+ def call(node, answers = {})
55
+ source = source_answers(node, answers)
56
+ model = resolve_model(node)
57
+ temperature = node.respond_to?(:temperature) ? (node.temperature || 0.2) : 0.2
58
+ max_tokens = node.respond_to?(:max_tokens) ? (node.max_tokens || DEFAULT_MAX_TOKENS) : DEFAULT_MAX_TOKENS
59
+
60
+ response = call_api(
61
+ model: model,
62
+ system: build_system_prompt(node),
63
+ user: build_user_prompt(node, source, answers),
64
+ temperature: temperature,
65
+ max_tokens: max_tokens
66
+ )
67
+
68
+ result = parse_response(response)
69
+ validate_output!(node, result)
70
+ result
71
+ end
72
+
73
+ private
74
+
75
+ def resolve_model(node)
76
+ return @default_model unless node.respond_to?(:model) && node.model
77
+
78
+ MODEL_MAP[node.model.to_sym] || node.model.to_s
79
+ end
80
+
81
+ def build_system_prompt(node)
82
+ "You are a data extraction assistant for a questionnaire engine. " \
83
+ "Your job is to analyze user input and extract structured data." +
84
+ schema_instruction(node)
85
+ end
86
+
87
+ def schema_instruction(node)
88
+ if node.respond_to?(:schema) && node.schema
89
+ schema_json = node.schema.fields.transform_values(&:to_s)
90
+ "\n\nYou MUST respond with ONLY a valid JSON object matching this schema:\n" \
91
+ "#{JSON.pretty_generate(schema_json)}\n\n" \
92
+ "Do not include any text before or after the JSON. No markdown fences. Just the raw JSON object."
93
+ else
94
+ "\n\nRespond with a valid JSON object containing your analysis. " \
95
+ "No markdown fences. Just the raw JSON object."
96
+ end
97
+ end
98
+
99
+ def build_user_prompt(node, source, all_answers)
100
+ parts = []
101
+ parts << "Task: #{node.prompt}" if node.respond_to?(:prompt) && node.prompt
102
+
103
+ if source.is_a?(Hash) && source.any?
104
+ parts << "\nSource data from previous answers:"
105
+ source.each { |key, value| parts << " #{key}: #{value.inspect}" }
106
+ end
107
+
108
+ if node.respond_to?(:schema) && node.schema
109
+ parts << "\nExtract these fields from the source data:"
110
+ node.schema.fields.each { |field, type| parts << " #{field} (#{type})" }
111
+ end
112
+
113
+ if node.respond_to?(:from_all) && node.from_all && all_answers.any?
114
+ parts << "\nAll collected answers:"
115
+ all_answers.each { |key, value| parts << " #{key}: #{value.inspect}" }
116
+ end
117
+
118
+ parts.join("\n")
119
+ end
120
+
121
+ def call_api(model:, system:, user:, temperature:, max_tokens:)
122
+ uri = URI(API_URL)
123
+ http = Net::HTTP.new(uri.host, uri.port)
124
+ http.use_ssl = true
125
+ http.read_timeout = 30
126
+ http.open_timeout = 10
127
+
128
+ request = Net::HTTP::Post.new(uri)
129
+ request["Content-Type"] = "application/json"
130
+ request["x-api-key"] = @api_key
131
+ request["anthropic-version"] = API_VERSION
132
+ request.body = JSON.generate(
133
+ model: model,
134
+ max_tokens: max_tokens,
135
+ temperature: temperature,
136
+ system: system,
137
+ messages: [{ role: "user", content: user }]
138
+ )
139
+
140
+ warn "[inquirex-llm] Calling #{model}..." if ENV["INQUIREX_DEBUG"]
141
+
142
+ response = http.request(request)
143
+ unless response.is_a?(Net::HTTPSuccess)
144
+ raise Errors::AdapterError, "Anthropic API error #{response.code}: #{response.body}"
145
+ end
146
+
147
+ JSON.parse(response.body)
148
+ end
149
+
150
+ def parse_response(api_response)
151
+ content = api_response["content"]
152
+ text_block = content.is_a?(Array) ? content.find { |c| c["type"] == "text" } : nil
153
+ raise Errors::AdapterError, "No text content in Anthropic response" unless text_block
154
+
155
+ raw_text = text_block["text"].to_s.strip
156
+ raw_text = raw_text.gsub(/\A```(?:json)?\s*\n?/, "").gsub(/\n?```\s*\z/, "").strip
157
+
158
+ parsed = JSON.parse(raw_text, symbolize_names: true)
159
+ raise Errors::AdapterError, "Expected JSON object from LLM, got #{parsed.class}" unless parsed.is_a?(Hash)
160
+
161
+ parsed
162
+ rescue JSON::ParserError => e
163
+ raise Errors::AdapterError,
164
+ "Failed to parse LLM response as JSON: #{e.message}\nRaw: #{raw_text.inspect}"
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "inquirex/llm/adapter"
7
+
8
+ module Inquirex
9
+ module LLM
10
+ # OpenAI Chat Completions adapter for inquirex-llm.
11
+ #
12
+ # Uses the Chat Completions API with response_format: { type: "json_object" }
13
+ # so the model is constrained to return a valid JSON object — more reliable
14
+ # than prompt-only "please return JSON" approaches for structured extraction.
15
+ #
16
+ # Usage:
17
+ # adapter = Inquirex::LLM::OpenAIAdapter.new(
18
+ # api_key: ENV["OPENAI_API_KEY"],
19
+ # model: "gpt-4o-mini"
20
+ # )
21
+ # result = adapter.call(engine.current_step, engine.answers)
22
+ class OpenAIAdapter < Adapter
23
+ API_URL = "https://api.openai.com/v1/chat/completions"
24
+ DEFAULT_MODEL = "gpt-4o-mini"
25
+ DEFAULT_MAX_TOKENS = 2048
26
+
27
+ # Maps Inquirex DSL model symbols to concrete OpenAI model ids. Accepts
28
+ # Claude symbols too — we substitute sensible OpenAI equivalents so flow
29
+ # definitions written against Anthropic still run against this adapter.
30
+ MODEL_MAP = {
31
+ gpt_4o: "gpt-4o",
32
+ gpt_4o_mini: "gpt-4o-mini",
33
+ gpt_4_1: "gpt-4.1",
34
+ gpt_4_1_mini: "gpt-4.1-mini",
35
+ claude_sonnet: "gpt-4o",
36
+ claude_haiku: "gpt-4o-mini",
37
+ claude_opus: "gpt-4o"
38
+ }.freeze
39
+
40
+ # @param api_key [String, nil] defaults to ENV["OPENAI_API_KEY"]
41
+ # @param model [String, nil] default model id when a node does not specify one
42
+ def initialize(api_key: nil, model: nil)
43
+ super()
44
+ @api_key = api_key || ENV.fetch("OPENAI_API_KEY") {
45
+ raise ArgumentError, "OPENAI_API_KEY is required (pass api_key: or set the env var)"
46
+ }
47
+ @default_model = model || DEFAULT_MODEL
48
+ end
49
+
50
+ # @param node [Inquirex::LLM::Node] the current LLM step
51
+ # @param answers [Hash] all collected answers so far
52
+ # @return [Hash] structured data matching the node's schema
53
+ # @raise [Errors::AdapterError] on API / parse failures
54
+ # @raise [Errors::SchemaViolationError] when the LLM output misses schema fields
55
+ def call(node, answers = {})
56
+ source = source_answers(node, answers)
57
+ model = resolve_model(node)
58
+ temperature = node.respond_to?(:temperature) ? (node.temperature || 0.2) : 0.2
59
+ max_tokens = node.respond_to?(:max_tokens) ? (node.max_tokens || DEFAULT_MAX_TOKENS) : DEFAULT_MAX_TOKENS
60
+
61
+ response = call_api(
62
+ model: model,
63
+ system: build_system_prompt(node),
64
+ user: build_user_prompt(node, source, answers),
65
+ temperature: temperature,
66
+ max_tokens: max_tokens
67
+ )
68
+
69
+ result = parse_response(response)
70
+ validate_output!(node, result)
71
+ result
72
+ end
73
+
74
+ private
75
+
76
+ def resolve_model(node)
77
+ return @default_model unless node.respond_to?(:model) && node.model
78
+
79
+ MODEL_MAP[node.model.to_sym] || node.model.to_s
80
+ end
81
+
82
+ def build_system_prompt(node)
83
+ "You are a data extraction assistant for a questionnaire engine. " \
84
+ "Your job is to analyze user input and extract structured data. " \
85
+ "You MUST respond with a single valid JSON object and nothing else." +
86
+ schema_instruction(node)
87
+ end
88
+
89
+ def schema_instruction(node)
90
+ if node.respond_to?(:schema) && node.schema
91
+ schema_json = node.schema.fields.transform_values(&:to_s)
92
+ "\n\nThe JSON object MUST match this schema exactly (same keys, appropriate types):\n" \
93
+ "#{JSON.pretty_generate(schema_json)}\n\n" \
94
+ "Every key in the schema must be present in your output. Use null, \"\", 0, or [] " \
95
+ "for values the source text does not provide."
96
+ else
97
+ ""
98
+ end
99
+ end
100
+
101
+ def build_user_prompt(node, source, all_answers)
102
+ parts = []
103
+ parts << "Task: #{node.prompt}" if node.respond_to?(:prompt) && node.prompt
104
+
105
+ if source.is_a?(Hash) && source.any?
106
+ parts << "\nSource data from previous answers:"
107
+ source.each { |key, value| parts << " #{key}: #{value.inspect}" }
108
+ end
109
+
110
+ if node.respond_to?(:schema) && node.schema
111
+ parts << "\nExtract these fields as a JSON object:"
112
+ node.schema.fields.each { |field, type| parts << " #{field} (#{type})" }
113
+ end
114
+
115
+ if node.respond_to?(:from_all) && node.from_all && all_answers.any?
116
+ parts << "\nAll collected answers:"
117
+ all_answers.each { |key, value| parts << " #{key}: #{value.inspect}" }
118
+ end
119
+
120
+ parts << "\nReturn ONLY the JSON object."
121
+ parts.join("\n")
122
+ end
123
+
124
+ def call_api(model:, system:, user:, temperature:, max_tokens:)
125
+ uri = URI(API_URL)
126
+ http = Net::HTTP.new(uri.host, uri.port)
127
+ http.use_ssl = true
128
+ http.read_timeout = 30
129
+ http.open_timeout = 10
130
+
131
+ request = Net::HTTP::Post.new(uri)
132
+ request["Content-Type"] = "application/json"
133
+ request["Authorization"] = "Bearer #{@api_key}"
134
+ request.body = JSON.generate(
135
+ model: model,
136
+ max_tokens: max_tokens,
137
+ temperature: temperature,
138
+ response_format: { type: "json_object" },
139
+ messages: [
140
+ { role: "system", content: system },
141
+ { role: "user", content: user }
142
+ ]
143
+ )
144
+
145
+ warn "[inquirex-llm] Calling OpenAI #{model}..." if ENV["INQUIREX_DEBUG"]
146
+
147
+ response = http.request(request)
148
+ unless response.is_a?(Net::HTTPSuccess)
149
+ raise Errors::AdapterError, "OpenAI API error #{response.code}: #{response.body}"
150
+ end
151
+
152
+ JSON.parse(response.body)
153
+ end
154
+
155
+ def parse_response(api_response)
156
+ choices = api_response["choices"]
157
+ message = choices.is_a?(Array) ? choices.first&.dig("message") : nil
158
+ raw_text = message&.dig("content")
159
+ raise Errors::AdapterError, "No message content in OpenAI response" unless raw_text
160
+
161
+ raw_text = raw_text.to_s.strip
162
+ raw_text = raw_text.gsub(/\A```(?:json)?\s*\n?/, "").gsub(/\n?```\s*\z/, "").strip
163
+
164
+ parsed = JSON.parse(raw_text, symbolize_names: true)
165
+ raise Errors::AdapterError, "Expected JSON object from LLM, got #{parsed.class}" unless parsed.is_a?(Hash)
166
+
167
+ parsed
168
+ rescue JSON::ParserError => e
169
+ raise Errors::AdapterError,
170
+ "Failed to parse LLM response as JSON: #{e.message}\nRaw: #{raw_text.inspect}"
171
+ end
172
+ end
173
+ end
174
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Inquirex
4
4
  module LLM
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/lib/inquirex/llm.rb CHANGED
@@ -9,6 +9,8 @@ require_relative "llm/schema"
9
9
  require_relative "llm/node"
10
10
  require_relative "llm/adapter"
11
11
  require_relative "llm/null_adapter"
12
+ require_relative "llm/anthropic_adapter"
13
+ require_relative "llm/openai_adapter"
12
14
  require_relative "llm/dsl/llm_step_builder"
13
15
  require_relative "llm/dsl/flow_builder"
14
16
 
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.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -35,6 +35,7 @@ extensions: []
35
35
  extra_rdoc_files: []
36
36
  files:
37
37
  - ".relaxed_rubocop.yml"
38
+ - ".rubocop_todo.yml"
38
39
  - ".secrets.baseline"
39
40
  - CHANGELOG.md
40
41
  - LICENSE.txt
@@ -47,11 +48,13 @@ files:
47
48
  - lib/inquirex-llm.rb
48
49
  - lib/inquirex/llm.rb
49
50
  - lib/inquirex/llm/adapter.rb
51
+ - lib/inquirex/llm/anthropic_adapter.rb
50
52
  - lib/inquirex/llm/dsl/flow_builder.rb
51
53
  - lib/inquirex/llm/dsl/llm_step_builder.rb
52
54
  - lib/inquirex/llm/errors.rb
53
55
  - lib/inquirex/llm/node.rb
54
56
  - lib/inquirex/llm/null_adapter.rb
57
+ - lib/inquirex/llm/openai_adapter.rb
55
58
  - lib/inquirex/llm/schema.rb
56
59
  - lib/inquirex/llm/version.rb
57
60
  - sig/inquirex/llm.rbs