From 090ca53bdeb712c611aed28a53fabceefd2652c4 Mon Sep 17 00:00:00 2001 From: aakashb95 Date: Fri, 10 Jul 2026 23:11:25 +0530 Subject: [PATCH] feat(llm): configure GPT-5.6 reasoning defaults --- config/installation_config.yml | 2 +- config/llm.yml | 79 +++++++-- config/llm_models.json | 161 +++++++++++++++++- lib/llm/config.rb | 2 +- lib/llm/feature_router.rb | 3 +- lib/llm/models.rb | 7 +- lib/llm_constants.rb | 2 +- .../captain/preferences_controller_spec.rb | 12 +- spec/enterprise/models/account_spec.rb | 2 +- .../models/concerns/agentable_spec.rb | 2 +- .../services/llm/base_ai_service_spec.rb | 2 +- spec/lib/llm/feature_router_spec.rb | 19 ++- spec/lib/llm/models_spec.rb | 29 +++- spec/models/account_spec.rb | 4 +- 14 files changed, 289 insertions(+), 37 deletions(-) diff --git a/config/installation_config.yml b/config/installation_config.yml index cfe64767f..3b11e1d7b 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -189,7 +189,7 @@ type: secret - name: CAPTAIN_OPEN_AI_MODEL display_title: 'OpenAI Model' - description: 'The OpenAI model configured for use in Captain AI. Default: gpt-4.1-mini' + description: 'The OpenAI model configured for use in Captain AI. Default: gpt-5.6-luna' locked: false - name: CAPTAIN_OPEN_AI_ENDPOINT display_title: 'OpenAI API Endpoint (optional)' diff --git a/config/llm.yml b/config/llm.yml index 2be3d86c7..86455f25b 100644 --- a/config/llm.yml +++ b/config/llm.yml @@ -35,6 +35,18 @@ models: provider: openai display_name: 'GPT-5.2' credit_multiplier: 3 + gpt-5.6-luna: + provider: openai + display_name: 'GPT-5.6 Luna' + credit_multiplier: 2 + gpt-5.6-terra: + provider: openai + display_name: 'GPT-5.6 Terra' + credit_multiplier: 3 + gpt-5.6-sol: + provider: openai + display_name: 'GPT-5.6 Sol' + credit_multiplier: 5 claude-haiku-4.5: provider: anthropic display_name: 'Claude Haiku 4.5' @@ -78,11 +90,14 @@ features: gpt-4.1, gpt-5.1, gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, claude-haiku-4.5, gemini-3-flash, gemini-3-pro, ] - default: gpt-4.1-mini + default: gpt-5.6-luna + reasoning_effort: low assistant: models: [ @@ -91,12 +106,16 @@ features: gpt-4.1, gpt-5.1, gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, + gpt-5.6-sol, claude-haiku-4.5, claude-sonnet-4.5, gemini-3-flash, gemini-3-pro, ] - default: gpt-4.1 + default: gpt-5.6-terra + reasoning_effort: medium copilot: models: [ @@ -105,16 +124,28 @@ features: gpt-4.1, gpt-5.1, gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, + gpt-5.6-sol, claude-haiku-4.5, claude-sonnet-4.5, gemini-3-flash, gemini-3-pro, ] - default: gpt-4.1 + default: gpt-5.6-terra + reasoning_effort: medium label_suggestion: models: - [gpt-4.1-nano, gpt-4.1-mini, gpt-5-mini, gemini-3-flash, claude-haiku-4.5] - default: gpt-4.1-mini + [ + gpt-4.1-nano, + gpt-4.1-mini, + gpt-5-mini, + gpt-5.6-luna, + gemini-3-flash, + claude-haiku-4.5, + ] + default: gpt-5.6-luna + reasoning_effort: low document_faq_generation: models: [ @@ -123,12 +154,16 @@ features: gpt-4.1, gpt-5.1, gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, + gpt-5.6-sol, claude-haiku-4.5, claude-sonnet-4.5, gemini-3-flash, gemini-3-pro, ] - default: gpt-4.1-mini + default: gpt-5.6-terra + reasoning_effort: medium conversation_faq_generation: models: [ @@ -137,15 +172,20 @@ features: gpt-4.1, gpt-5.1, gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, + gpt-5.6-sol, claude-haiku-4.5, claude-sonnet-4.5, gemini-3-flash, gemini-3-pro, ] - default: gpt-5.2 + default: gpt-5.6-terra + reasoning_effort: medium pdf_faq_generation: models: [gpt-4.1-mini, gpt-5-mini, gpt-4.1, gpt-5.1, gpt-5.2] default: gpt-4.1-mini + reasoning_effort: medium help_center_article_generation: models: [ @@ -154,19 +194,34 @@ features: gpt-4.1, gpt-5.1, gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, + gpt-5.6-sol, claude-haiku-4.5, claude-sonnet-4.5, gemini-3-flash, gemini-3-pro, ] - default: gpt-5.2 + default: gpt-5.6-terra + reasoning_effort: medium onboarding_content_generation: models: - [gpt-4.1, gpt-4.1-mini, gpt-5-mini, gpt-5.1, gpt-5.2] - default: gpt-4.1 + [ + gpt-4.1, + gpt-4.1-mini, + gpt-5-mini, + gpt-5.1, + gpt-5.2, + gpt-5.6-luna, + gpt-5.6-terra, + gpt-5.6-sol, + ] + default: gpt-5.6-terra + reasoning_effort: medium help_center_query_translation: - models: [gpt-4.1-nano, gpt-4.1-mini, gpt-5-mini] - default: gpt-4.1-nano + models: [gpt-4.1-nano, gpt-4.1-mini, gpt-5-mini, gpt-5.6-luna] + default: gpt-5.6-luna + reasoning_effort: low audio_transcription: models: [gpt-4o-mini-transcribe, whisper-1] default: gpt-4o-mini-transcribe diff --git a/config/llm_models.json b/config/llm_models.json index 00c2038e5..26681dec3 100644 --- a/config/llm_models.json +++ b/config/llm_models.json @@ -27970,6 +27970,165 @@ "owned_by": "system" } }, + { + "id": "gpt-5.6-luna", + "name": "GPT-5.6 Luna", + "provider": "openai", + "family": "gpt", + "created_at": null, + "context_window": 1050000, + "max_output_tokens": 128000, + "knowledge_cutoff": "2026-02-16", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "structured_output", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 1, + "output_per_million": 6 + } + } + }, + "metadata": { + "object": "model", + "owned_by": "system", + "source": "openai_docs", + "provider_id": "openai", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-07-10", + "cost": { + "input": 1, + "output": 6 + }, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + }, + "knowledge": "2026-02-16" + } + }, + { + "id": "gpt-5.6-terra", + "name": "GPT-5.6 Terra", + "provider": "openai", + "family": "gpt", + "created_at": null, + "context_window": 1050000, + "max_output_tokens": 128000, + "knowledge_cutoff": "2026-02-16", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "structured_output", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 2.5, + "output_per_million": 15 + } + } + }, + "metadata": { + "object": "model", + "owned_by": "system", + "source": "openai_docs", + "provider_id": "openai", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-07-10", + "cost": { + "input": 2.5, + "output": 15 + }, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + }, + "knowledge": "2026-02-16" + } + }, + { + "id": "gpt-5.6-sol", + "name": "GPT-5.6 Sol", + "provider": "openai", + "family": "gpt", + "created_at": null, + "context_window": 1050000, + "max_output_tokens": 128000, + "knowledge_cutoff": "2026-02-16", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "structured_output", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 5, + "output_per_million": 30 + } + } + }, + "metadata": { + "object": "model", + "owned_by": "system", + "source": "openai_docs", + "provider_id": "openai", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-07-10", + "cost": { + "input": 5, + "output": 30 + }, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + }, + "knowledge": "2026-02-16" + } + }, { "id": "gpt-audio", "name": "gpt-audio", @@ -61509,4 +61668,4 @@ "owned_by": "xai" } } -] \ No newline at end of file +] diff --git a/lib/llm/config.rb b/lib/llm/config.rb index 6528e90c8..f235b9b35 100644 --- a/lib/llm/config.rb +++ b/lib/llm/config.rb @@ -1,7 +1,7 @@ require 'ruby_llm' module Llm::Config - DEFAULT_MODEL = 'gpt-4.1-mini'.freeze + DEFAULT_MODEL = 'gpt-5.6-luna'.freeze class << self def initialized? diff --git a/lib/llm/feature_router.rb b/lib/llm/feature_router.rb index d75b06dca..4fc6585bb 100644 --- a/lib/llm/feature_router.rb +++ b/lib/llm/feature_router.rb @@ -1,7 +1,7 @@ module Llm::FeatureRouter class UnknownFeatureError < StandardError; end - CAPTAIN_V2_ASSISTANT_MODEL = 'gpt-5.2'.freeze + CAPTAIN_V2_ASSISTANT_MODEL = 'gpt-5.6-terra'.freeze class << self def resolve(feature:, account: nil) @@ -17,6 +17,7 @@ module Llm::FeatureRouter feature: feature_key, provider: Llm::Models.provider_for(model), model: model, + reasoning_effort: Llm::Models.reasoning_effort_for(feature_key), source: source } end diff --git a/lib/llm/models.rb b/lib/llm/models.rb index 896014262..8ece469e3 100644 --- a/lib/llm/models.rb +++ b/lib/llm/models.rb @@ -15,6 +15,10 @@ module Llm::Models features.dig(feature.to_s, 'default') end + def reasoning_effort_for(feature) + features.dig(feature.to_s, 'reasoning_effort') + end + def models_for(feature) features.dig(feature.to_s, 'models') || [] end @@ -46,7 +50,8 @@ module Llm::Models credit_multiplier: model['credit_multiplier'] } end, - default: feature['default'] + default: feature['default'], + reasoning_effort: feature['reasoning_effort'] } end end diff --git a/lib/llm_constants.rb b/lib/llm_constants.rb index 241f3ccba..19ff6fa4f 100644 --- a/lib/llm_constants.rb +++ b/lib/llm_constants.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module LlmConstants - DEFAULT_MODEL = 'gpt-4.1' + DEFAULT_MODEL = 'gpt-5.6-luna' DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small' PDF_PROCESSING_MODEL = 'gpt-4.1-mini' diff --git a/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb b/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb index 6a28c60da..46dd4b41a 100644 --- a/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb @@ -44,10 +44,12 @@ RSpec.describe 'Api::V1::Accounts::Captain::Preferences', type: :request do expect(json_response).to have_key(:providers) expect(json_response).to have_key(:models) expect(json_response).to have_key(:features) + expect(json_response[:models].keys).to include(:'gpt-5.6-luna', :'gpt-5.6-terra', :'gpt-5.6-sol') + expect(json_response.dig(:features, :assistant, :models).pluck(:id)).to include('gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol') end it 'returns effective model provider and source for each feature' do - account.update!(captain_models: { 'editor' => 'gpt-4.1' }) + account.update!(captain_models: { 'editor' => 'gpt-5.6-terra' }) get "/api/v1/accounts/#{account.id}/captain/preferences", headers: admin.create_new_auth_token, @@ -55,15 +57,17 @@ RSpec.describe 'Api::V1::Accounts::Captain::Preferences', type: :request do expect(response).to have_http_status(:success) expect(json_response.dig(:features, :editor)).to include( - model: 'gpt-4.1', - selected: 'gpt-4.1', + model: 'gpt-5.6-terra', + selected: 'gpt-5.6-terra', provider: 'openai', + reasoning_effort: 'low', source: 'account_override' ) expect(json_response.dig(:features, :label_suggestion)).to include( model: Llm::Models.default_model_for('label_suggestion'), selected: Llm::Models.default_model_for('label_suggestion'), provider: 'openai', + reasoning_effort: 'low', source: 'default' ) end @@ -81,7 +85,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::Preferences', type: :request do ) end - it 'returns GPT-5.2 as the assistant default for V2 accounts' do + it 'returns the Captain V2 assistant default for V2 accounts' do account.enable_features!('captain_integration_v2') get "/api/v1/accounts/#{account.id}/captain/preferences", diff --git a/spec/enterprise/models/account_spec.rb b/spec/enterprise/models/account_spec.rb index 7c57e217f..49352d361 100644 --- a/spec/enterprise/models/account_spec.rb +++ b/spec/enterprise/models/account_spec.rb @@ -258,7 +258,7 @@ RSpec.describe Account, type: :model do expect(account).to be_feature_enabled('captain_integration') expect(account).to be_feature_enabled('captain_integration_v2') - expect(account.captain_preferences[:models]['assistant']).to eq('gpt-5.2') + expect(account.captain_preferences[:models]['assistant']).to eq(Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL) expect(account.captain_models).to be_nil end diff --git a/spec/enterprise/models/concerns/agentable_spec.rb b/spec/enterprise/models/concerns/agentable_spec.rb index f2145ff85..dd028d309 100644 --- a/spec/enterprise/models/concerns/agentable_spec.rb +++ b/spec/enterprise/models/concerns/agentable_spec.rb @@ -183,7 +183,7 @@ RSpec.describe Concerns::Agentable do create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano') account.enable_features!('captain_integration_v2') - expect(dummy_instance.send(:agent_model)).to eq('gpt-5.2') + expect(dummy_instance.send(:agent_model)).to eq(Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL) expect(account.reload.captain_models).to be_nil end diff --git a/spec/enterprise/services/llm/base_ai_service_spec.rb b/spec/enterprise/services/llm/base_ai_service_spec.rb index d4c07d336..a60d84559 100644 --- a/spec/enterprise/services/llm/base_ai_service_spec.rb +++ b/spec/enterprise/services/llm/base_ai_service_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Llm::BaseAiService do create(:installation_config, name: 'CAPTAIN_OPEN_AI_MODEL', value: 'gpt-4.1-nano') account.enable_features!('captain_integration_v2') - expect(described_class.new(feature: 'assistant', account: account).model).to eq('gpt-5.2') + expect(described_class.new(feature: 'assistant', account: account).model).to eq(Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL) expect(account.reload.captain_models).to be_nil end diff --git a/spec/lib/llm/feature_router_spec.rb b/spec/lib/llm/feature_router_spec.rb index ea2b9f91f..e202ca5dc 100644 --- a/spec/lib/llm/feature_router_spec.rb +++ b/spec/lib/llm/feature_router_spec.rb @@ -12,25 +12,27 @@ RSpec.describe Llm::FeatureRouter do expect(resolved).to eq( feature: 'editor', provider: 'openai', - model: 'gpt-4.1-mini', + model: 'gpt-5.6-luna', + reasoning_effort: 'low', source: :default ) end it 'uses a valid account model override' do - account.update!(captain_models: { 'editor' => 'gpt-4.1' }) + account.update!(captain_models: { 'editor' => 'gpt-5.6-terra' }) resolved = described_class.resolve(feature: 'editor', account: account) expect(resolved).to include( feature: 'editor', provider: 'openai', - model: 'gpt-4.1', + model: 'gpt-5.6-terra', + reasoning_effort: 'low', source: :account_override ) end - it 'resolves GPT-5.2 as the assistant default when Captain V2 is enabled without storing an account override' do + it 'resolves the Captain V2 assistant default without storing an account override' do account.enable_features!('captain_integration_v2') resolved = described_class.resolve(feature: 'assistant', account: account) @@ -38,7 +40,8 @@ RSpec.describe Llm::FeatureRouter do expect(resolved).to include( feature: 'assistant', provider: 'openai', - model: 'gpt-5.2', + model: described_class::CAPTAIN_V2_ASSISTANT_MODEL, + reasoning_effort: 'medium', source: :default ) expect(account.reload.captain_models).to be_nil @@ -62,7 +65,8 @@ RSpec.describe Llm::FeatureRouter do resolved = described_class.resolve(feature: 'editor', account: account) expect(resolved).to include( - model: 'gpt-4.1-mini', + model: 'gpt-5.6-luna', + reasoning_effort: 'low', source: :default ) end @@ -73,7 +77,8 @@ RSpec.describe Llm::FeatureRouter do resolved = described_class.resolve(feature: 'editor', account: account) expect(resolved).to include( - model: 'gpt-4.1-mini', + model: 'gpt-5.6-luna', + reasoning_effort: 'low', source: :default ) end diff --git a/spec/lib/llm/models_spec.rb b/spec/lib/llm/models_spec.rb index 5692bee9c..a2c02b198 100644 --- a/spec/lib/llm/models_spec.rb +++ b/spec/lib/llm/models_spec.rb @@ -27,12 +27,33 @@ RSpec.describe Llm::Models do end it 'routes document and conversation FAQ generation independently' do - expect(described_class.default_model_for('document_faq_generation')).to eq('gpt-4.1-mini') - expect(described_class.default_model_for('conversation_faq_generation')).to eq('gpt-5.2') + expect(described_class.default_model_for('document_faq_generation')).to eq('gpt-5.6-terra') + expect(described_class.default_model_for('conversation_faq_generation')).to eq('gpt-5.6-terra') + end + + it 'sets reasoning effort for every text generation feature' do + features_without_reasoning = %w[audio_transcription help_center_search] + + described_class.features.each_key do |feature_key| + if features_without_reasoning.include?(feature_key) + expect(described_class.reasoning_effort_for(feature_key)).to be_nil + else + expect(described_class.reasoning_effort_for(feature_key)).to be_present + end + end + end + + it 'exposes GPT-5.6 models for Captain V2 workflows without changing the legacy PDF path' do + expect(described_class.models_for('assistant')).to include('gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol') + expect(described_class.models_for('pdf_faq_generation')).not_to include('gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol') end end describe '.models' do + it 'includes the GPT-5.6 model family' do + expect(described_class.models.keys).to include('gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol') + end + it 'references existing providers from every model' do missing_providers = described_class.models.filter_map do |model_name, config| provider = config['provider'] @@ -49,7 +70,9 @@ RSpec.describe Llm::Models do it 'returns model metadata for a feature' do config = described_class.feature_config('editor') - expect(config[:default]).to eq('gpt-4.1-mini') + expect(config[:default]).to eq('gpt-5.6-luna') + expect(config[:reasoning_effort]).to eq('low') + expect(config[:models].pluck(:id)).to include('gpt-5.6-luna', 'gpt-5.6-terra') expect(config[:models].first).to include( id: 'gpt-4.1-mini', display_name: 'GPT-4.1 Mini', diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 9adc98cc0..1df7423b3 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -393,10 +393,10 @@ RSpec.describe Account do end end - it 'returns GPT-5.2 for assistant when Captain V2 is enabled' do + it 'returns the Captain V2 default for assistant when Captain V2 is enabled' do account.enable_features!('captain_integration_v2') - expect(account.captain_preferences[:models]['assistant']).to eq('gpt-5.2') + expect(account.captain_preferences[:models]['assistant']).to eq(Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL) expect(account.reload.captain_models).to be_nil end end