aireview 2.2.0 → 2.2.1

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: c37a072f5cb03c8a4e95944a3c6fc5386d1abe6c340c63ebee9715db5d44ec6a
4
- data.tar.gz: b2e2ef104f8e4310dafc0dd5041a899c7ff06af5d2098cff25e0e38f010f9847
3
+ metadata.gz: 7919de6ffa17aab9f5250ebf07dfec9ce83db9313ef01e6ba93a3b131da927ce
4
+ data.tar.gz: 348cda52953ff2d22ba56fb16b192876249efa30c5ba77f6f0736c1b96e5f569
5
5
  SHA512:
6
- metadata.gz: '09524e49ed6896e32e6e73d3fcc5f5e27d19eaa255273017acf699e8e6992f44a8a950e34d96f446066e58b31a6ee625188891e913ac3a4c0db27ac2b39e95a8'
7
- data.tar.gz: 4a193a705c06609c07ceb2833211d51d124e1299378368feb50fc08e0fac48a6ff09f553b47406b0bd367e83ebfe7b55fd66d018f4781551725aa27234e95113
6
+ metadata.gz: 372867e5a1547b4d595312498107dd8c33c6fe6721efb8f4c215850660bcf57706f5a864c9d9b7d46a9d2a1e29fdf18ab31e9ddb77c1859237ddbe5d979b2946
7
+ data.tar.gz: b3b079dbb2b5b92ffe39fc36c6de224d9bf2268e11b353129236e15b5c14689a9067012da053b8415bb53f64b2f52247ef252062831e9e73d789b943a611ed0a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.1
4
+
5
+ - OpenAI reasoning models (`o1`, `o3`, `gpt-5`…) and search models
6
+ (`gpt-4o-search-preview`…) get no temperature: RubyLLM 2 sends it as given
7
+ (1.x replaced it with 1.0 or dropped it), and these models reject any
8
+ other value with a bad request, which failed the run without trying a
9
+ fallback model. The RubyLLM registry says which models take a
10
+ temperature; a model it does not know is judged by its name, as 1.x did.
11
+ `models check` is fixed the same way.
12
+
3
13
  ## 2.2.0
4
14
 
5
15
  - Jev shadow mode (`llm.jev.shadow`, `LLM_JEV_SHADOW`, key `JEV_API_KEY`):
data/README.md CHANGED
@@ -226,6 +226,12 @@ or without auth when that is not set, and needs no key to start. Without an
226
226
  `api_base` a model goes to its provider's API, or to `LLM_API_BASE` for
227
227
  Gemini, OpenAI and OpenRouter as before.
228
228
 
229
+ A stage's temperature goes only to the models that take one: OpenAI
230
+ reasoning models (`o1`, `o3`, `gpt-5`…) and search models
231
+ (`gpt-4o-search-preview`…) accept no other, so they get none and run with
232
+ their own default. The RubyLLM registry decides, and the name
233
+ for a model it does not know.
234
+
229
235
  Instead of an LLM the critic can be Jev (see "Critique engine").
230
236
 
231
237
  ### Local Ollama
@@ -17,6 +17,16 @@ module Aireview
17
17
  end
18
18
  end
19
19
 
20
+ # RubyLLM 2 sends the temperature as given, while 1.x replaced it with
21
+ # 1.0 for OpenAI reasoning models (o1, o3, gpt-5…) and dropped it for the
22
+ # search ones (gpt-4o-search-preview…), which take no other: such a
23
+ # request is a BadRequest, fatal for the router, and the stage would not
24
+ # even reach a fallback model. The RubyLLM registry knows which models
25
+ # take a temperature; where it does not (a server of your own,
26
+ # assume_model_exists, search models with an empty flag) the name
27
+ # decides, as in 1.x.
28
+ NO_TEMPERATURE_MODELS = %r{\A(?:openai/)?(?:o\d|gpt-5)|-search}
29
+
20
30
  def initialize(config:, logger: Logger.new($stderr))
21
31
  @config = config
22
32
  @logger = logger
@@ -26,16 +36,10 @@ module Aireview
26
36
  # Returns the RubyLLM answer; read it with LlmClient.content. A request
27
37
  # error is re-raised as is — LlmFailure classifies it.
28
38
  def request(prompt, candidate:, key:, timeout:, key_index: 0)
29
- load_ruby_llm
30
39
  stage = prompt.stage.to_s
31
40
  model = candidate.model
32
- @logger.info("LLM #{stage} request started (model=#{model}, temperature=#{prompt.temperature})")
33
- chat = build_chat(context: context(stage, candidate, key, key_index), stage: stage,
34
- model: model, provider: candidate.provider)
35
- chat = configure_reasoning(chat: chat, model: model, provider: candidate.provider)
36
- .with_temperature(prompt.temperature.to_f)
37
- .with_schema(prompt.schema)
38
- chat.with_instructions(prompt.system)
41
+ chat = prepare(prompt, candidate: candidate, key: key, key_index: key_index)
42
+ @logger.info("LLM #{stage} request started (model=#{model}, temperature=#{chat.temperature || 'model default'})")
39
43
  response = Timeout.timeout(timeout) { chat.ask(prompt.user) }
40
44
  @logger.info("LLM #{stage} request completed (model=#{model}#{token_counts(response)})")
41
45
  response
@@ -44,6 +48,21 @@ module Aireview
44
48
  raise
45
49
  end
46
50
 
51
+ # The request as it will go, without sending it: the chat with the model,
52
+ # key, schema, instructions and temperature set. chat.render builds it —
53
+ # that is how the specs check the real request without the network.
54
+ def prepare(prompt, candidate:, key:, key_index: 0)
55
+ load_ruby_llm
56
+ stage = prompt.stage.to_s
57
+ chat = build_chat(context: context(stage, candidate, key, key_index), stage: stage,
58
+ model: candidate.model, provider: candidate.provider)
59
+ chat = configure_reasoning(chat: chat, model: candidate.model, provider: candidate.provider)
60
+ .with_temperature(temperature_for(chat, prompt, candidate))
61
+ .with_schema(prompt.schema)
62
+ chat.with_instructions(prompt.system)
63
+ chat
64
+ end
65
+
47
66
  # The answer as RubyLLM 1.x gave it under a schema: the parsed JSON when
48
67
  # the text is JSON, the text itself otherwise (the pipeline repairs it).
49
68
  # RubyLLM 2 always returns the text, and a Hash that breaks the schema
@@ -82,6 +101,14 @@ module Aireview
82
101
  raise ConfigError, "Missing dependency: #{e.message}"
83
102
  end
84
103
 
104
+ # The temperature for a model that takes one (see NO_TEMPERATURE_MODELS);
105
+ # nil — its own default, left out of the request.
106
+ def temperature_for(chat, prompt, candidate)
107
+ accepts = chat.model.metadata[:temperature] if chat.model.respond_to?(:metadata)
108
+ accepts = !candidate.model.to_s.match?(NO_TEMPERATURE_MODELS) if accepts.nil?
109
+ accepts ? prompt.temperature.to_f : nil
110
+ end
111
+
85
112
  def configure_reasoning(chat:, model:, provider:)
86
113
  return chat unless provider == 'ollama' && model.start_with?('gpt-oss:')
87
114
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Aireview
3
- VERSION = '2.2.0'
3
+ VERSION = '2.2.1'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aireview
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Levenko