Skip to content

Commit a6af10f

Browse files
xfalcoxpento
authored andcommitted
FEATURE: Add support for 'minimal' reasoning effort in OpenAI models (#34179)
- Add 'minimal' as a supported reasoning effort value in LlmModel configuration - Update OpenAI endpoint validation to accept 'minimal' effort level - Implement dual payload format support: * Responses API: `{ reasoning: { effort: "value" } }` * Standard API: `{ reasoning_effort: "value" }` - Add comprehensive tests for reasoning effort payload formats - Update GPT-5 model configurations with max_output_tokens and pricing fixes
1 parent 2536f7e commit a6af10f

File tree

4 files changed

+71
-5
lines changed

4 files changed

+71
-5
lines changed

plugins/discourse-ai/app/models/llm_model.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def self.provider_params
5555
enable_responses_api: :checkbox,
5656
reasoning_effort: {
5757
type: :enum,
58-
values: %w[default low medium high],
58+
values: %w[default minimal low medium high],
5959
default: "default",
6060
},
6161
},

plugins/discourse-ai/lib/completions/endpoints/open_ai.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def disable_streaming?
6262
def reasoning_effort
6363
return @reasoning_effort if defined?(@reasoning_effort)
6464
@reasoning_effort = llm_model.lookup_custom_param("reasoning_effort")
65-
@reasoning_effort = nil if !%w[low medium high].include?(@reasoning_effort)
65+
@reasoning_effort = nil if !%w[minimal low medium high].include?(@reasoning_effort)
6666
@reasoning_effort
6767
end
6868

@@ -80,7 +80,13 @@ def model_uri
8080
def prepare_payload(prompt, model_params, dialect)
8181
payload = default_options.merge(model_params).merge(messages: prompt)
8282

83-
payload[:reasoning_effort] = reasoning_effort if reasoning_effort
83+
if reasoning_effort
84+
if responses_api?
85+
payload.merge!({ reasoning: { effort: reasoning_effort } })
86+
else
87+
payload.merge!({ reasoning_effort: reasoning_effort })
88+
end
89+
end
8490

8591
if @streaming_mode
8692
payload[:stream] = true

plugins/discourse-ai/lib/completions/llm.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ def presets
130130
tokens: 400_000,
131131
display_name: "GPT-5",
132132
input_cost: 1.25,
133-
cached_input_cost: 0.124,
133+
cached_input_cost: 0.125,
134134
output_cost: 10,
135+
max_output_tokens: 128_000,
135136
},
136137
{
137138
name: "gpt-5-mini",
@@ -140,14 +141,16 @@ def presets
140141
input_cost: 0.25,
141142
cached_input_cost: 0.025,
142143
output_cost: 2.0,
144+
max_output_tokens: 128_000,
143145
},
144146
{
145147
name: "gpt-5-nano",
146148
tokens: 400_000,
147-
display_name: "GPT-4.1 Nano",
149+
display_name: "GPT-5 Nano",
148150
input_cost: 0.05,
149151
cached_input_cost: 0.005,
150152
output_cost: 0.40,
153+
max_output_tokens: 128_000,
151154
},
152155
],
153156
tokenizer: DiscourseAi::Tokenizer::OpenAiTokenizer,

plugins/discourse-ai/spec/lib/completions/endpoints/open_ai_spec.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,4 +995,61 @@ def request_body(prompt, stream: false, tool_call: false)
995995
end
996996
end
997997
end
998+
999+
describe "reasoning effort payload format" do
1000+
let(:prompt) { compliance.generic_prompt }
1001+
let(:dialect) { compliance.dialect(prompt: prompt) }
1002+
1003+
it "uses reasoning object format for responses API" do
1004+
model.update!(provider_params: { enable_responses_api: true, reasoning_effort: "minimal" })
1005+
1006+
parsed_body = nil
1007+
stub_request(:post, "https://api.openai.com/v1/chat/completions").with(
1008+
body:
1009+
proc do |req_body|
1010+
parsed_body = JSON.parse(req_body, symbolize_names: true)
1011+
true
1012+
end,
1013+
).to_return(status: 200, body: { choices: [{ message: { content: "test" } }] }.to_json)
1014+
1015+
endpoint.perform_completion!(dialect, user)
1016+
1017+
expect(parsed_body[:reasoning]).to eq({ effort: "minimal" })
1018+
expect(parsed_body).not_to have_key(:reasoning_effort)
1019+
end
1020+
1021+
it "uses reasoning_effort field for standard API" do
1022+
model.update!(provider_params: { reasoning_effort: "low" })
1023+
1024+
parsed_body = nil
1025+
stub_request(:post, "https://api.openai.com/v1/chat/completions").with(
1026+
body:
1027+
proc do |req_body|
1028+
parsed_body = JSON.parse(req_body, symbolize_names: true)
1029+
true
1030+
end,
1031+
).to_return(status: 200, body: { choices: [{ message: { content: "test" } }] }.to_json)
1032+
1033+
endpoint.perform_completion!(dialect, user)
1034+
1035+
expect(parsed_body[:reasoning_effort]).to eq("low")
1036+
expect(parsed_body).not_to have_key(:reasoning)
1037+
end
1038+
1039+
it "omits reasoning parameters when not configured" do
1040+
parsed_body = nil
1041+
stub_request(:post, "https://api.openai.com/v1/chat/completions").with(
1042+
body:
1043+
proc do |req_body|
1044+
parsed_body = JSON.parse(req_body, symbolize_names: true)
1045+
true
1046+
end,
1047+
).to_return(status: 200, body: { choices: [{ message: { content: "test" } }] }.to_json)
1048+
1049+
endpoint.perform_completion!(dialect, user)
1050+
1051+
expect(parsed_body).not_to have_key(:reasoning)
1052+
expect(parsed_body).not_to have_key(:reasoning_effort)
1053+
end
1054+
end
9981055
end

0 commit comments

Comments
 (0)