Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.040"
VERSION = "0.261.044"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
2 changes: 2 additions & 0 deletions application/single_app/functions_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1503,11 +1503,13 @@
'model_endpoint_identity_header_name': DEFAULT_MODEL_ENDPOINT_IDENTITY_HEADER_NAME,
'model_endpoint_identity_header_value_type': DEFAULT_MODEL_ENDPOINT_IDENTITY_HEADER_VALUE_TYPE,
'model_endpoint_identity_header_hmac_secret': secrets.token_urlsafe(48),
'enable_default_model_for_new_conversations': False,

Check warning on line 1506 in application/single_app/functions_settings.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
'default_model_selection': {
'endpoint_id': '',
'model_id': '',
'provider': ''
},
'default_reasoning_effort': '',
'multi_endpoint_migrated_at': None,
'multi_endpoint_migration_notice': {
'enabled': False,
Expand Down
57 changes: 55 additions & 2 deletions application/single_app/route_backend_chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,42 @@
return resolved_reasoning_effort


def _normalize_default_reasoning_effort(reasoning_effort):
normalized_reasoning_effort = str(reasoning_effort or '').strip().lower()
if normalized_reasoning_effort in ('', 'none', 'minimal', 'low', 'medium', 'high'):
return normalized_reasoning_effort
return ''


def _apply_admin_new_conversation_model_defaults(settings, data, conversation_id, request_agent_info, *, is_retry=False):

Check warning on line 488 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
"""Apply admin model defaults when a normal chat request starts a new conversation."""

Check warning on line 489 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
if (
conversation_id

Check warning on line 491 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
or is_retry
or _has_chat_agent_selection(request_agent_info)

Check warning on line 493 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
or not settings.get('enable_multi_model_endpoints', False)

Check warning on line 494 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
or not settings.get('enable_default_model_for_new_conversations', False)

Check warning on line 495 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
):
return False

default_selection = settings.get('default_model_selection', {}) or {}
default_endpoint_id = str(default_selection.get('endpoint_id') or '').strip()
default_model_id = str(default_selection.get('model_id') or '').strip()
default_provider = str(default_selection.get('provider') or '').strip().lower()
if not (default_endpoint_id and default_model_id):
return False

data['model_endpoint_id'] = default_endpoint_id

Check warning on line 506 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
data['model_id'] = default_model_id
data['model_provider'] = default_provider
data['model_deployment'] = ''
default_reasoning_effort = _normalize_default_reasoning_effort(settings.get('default_reasoning_effort'))
if default_reasoning_effort:
data['reasoning_effort'] = None if default_reasoning_effort == 'none' else default_reasoning_effort
data['_admin_default_model_applied'] = True
return True


def _apply_response_length_for_model(api_params, response_length, model_name, provider=None, response_length_parameter=None):
normalized_response_length = normalize_model_response_length(response_length)
if not normalized_response_length:
Expand Down Expand Up @@ -14453,7 +14489,7 @@
if requested_endpoint_id:
if not (requested_model_id or requested_deployment):
raise ValueError('Selected model information is incomplete for the streaming request.')
selection_source = 'request'
selection_source = 'default' if data.get('_admin_default_model_applied') else 'request'
elif allow_default_selection:
default_selection = settings.get('default_model_selection', {}) or {}
default_endpoint_id = str(default_selection.get('endpoint_id') or '').strip()
Expand Down Expand Up @@ -16800,6 +16836,14 @@
conversation_id = getattr(g, 'conversation_id', None) or data.get('conversation_id')
if conversation_id is not None:
conversation_id = str(conversation_id).strip() or None
request_is_retry = bool(data.get('retry_user_message_id') or data.get('edited_user_message_id'))
_apply_admin_new_conversation_model_defaults(

Check warning on line 16840 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
settings,
data,
conversation_id,

Check warning on line 16843 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
request_agent_info,
is_retry=request_is_retry,
)
hybrid_search_enabled = data.get('hybrid_search')
web_search_enabled = data.get('web_search_enabled')
url_access_enabled = data.get('url_access_enabled')
Expand Down Expand Up @@ -16899,7 +16943,7 @@
retry_user_message_id = data.get('retry_user_message_id') or data.get('edited_user_message_id')
retry_thread_id = data.get('retry_thread_id')
retry_thread_attempt = data.get('retry_thread_attempt')
is_retry = bool(retry_user_message_id)
is_retry = request_is_retry
is_edit = bool(data.get('edited_user_message_id'))

if is_retry:
Expand Down Expand Up @@ -17869,6 +17913,7 @@
'model_icon': gpt_model_icon,
'response_length': gpt_response_length,
'reasoning_effort': reasoning_effort if reasoning_effort and reasoning_effort != 'none' else None,
'admin_default_applied': bool(data.get('_admin_default_model_applied')),
'streaming': 'Disabled'
}

Expand Down Expand Up @@ -21196,6 +21241,13 @@
data['active_group_id'] = initial_scope_context['active_group_id']
data['active_public_workspace_ids'] = list(initial_scope_context['active_public_workspace_ids'])
data['active_public_workspace_id'] = initial_scope_context['active_public_workspace_id']
_apply_admin_new_conversation_model_defaults(
settings,
data,
None if is_new_stream_conversation else finalized_conversation_id,
data.get('agent_info'),
is_retry=is_retry,
)
stream_session = CHAT_STREAM_REGISTRY.start_session(user_id, finalized_conversation_id)

request_message = (data.get('message') or '').strip()
Expand Down Expand Up @@ -22453,6 +22505,7 @@
'model_icon': gpt_model_icon,
'response_length': gpt_response_length,
'reasoning_effort': reasoning_effort if reasoning_effort and reasoning_effort != 'none' else None,
'admin_default_applied': bool(data.get('_admin_default_model_applied')),
'streaming': 'Enabled'
}

Expand Down
19 changes: 19 additions & 0 deletions application/single_app/route_frontend_admin_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,10 @@ def admin_settings():
'model_id': '',
'provider': ''
}
if 'enable_default_model_for_new_conversations' not in settings:
settings['enable_default_model_for_new_conversations'] = False
if 'default_reasoning_effort' not in settings:
settings['default_reasoning_effort'] = ''
if 'metadata_extraction_model_selection' not in settings or not isinstance(settings.get('metadata_extraction_model_selection'), dict):
settings['metadata_extraction_model_selection'] = {
'endpoint_id': '',
Expand Down Expand Up @@ -1868,6 +1872,19 @@ def parse_admin_int(raw_value, fallback_value, field_name="unknown", hard_defaul
'provider': ''
}

enable_default_model_for_new_conversations = (
enable_multi_model_endpoints
and form_data.get('enable_default_model_for_new_conversations') == 'on'
)
default_reasoning_effort = (
form_data.get('default_reasoning_effort', '').strip().lower()
)
if default_reasoning_effort not in ('', 'none', 'minimal', 'low', 'medium', 'high'):
flash('Default reasoning effort is not valid. Please select a supported value.', 'warning')
default_reasoning_effort = ''
if not enable_default_model_for_new_conversations:
default_reasoning_effort = ''

metadata_selection_json = form_data.get('metadata_extraction_model_selection_json', '{}')
parsed_metadata_model_selection = {}
try:
Expand Down Expand Up @@ -2520,7 +2537,9 @@ def is_valid_url(url):
'model_endpoint_identity_header_name': model_endpoint_identity_header_name,
'model_endpoint_identity_header_value_type': model_endpoint_identity_header_value_type,
'model_endpoint_identity_header_hmac_secret': settings.get('model_endpoint_identity_header_hmac_secret', ''),
'enable_default_model_for_new_conversations': enable_default_model_for_new_conversations,
'default_model_selection': normalized_default_model_selection,
'default_reasoning_effort': default_reasoning_effort,
'multi_endpoint_migrated_at': migrated_at,
'multi_endpoint_migration_notice': migration_notice,
'azure_apim_gpt_endpoint': form_data.get('azure_apim_gpt_endpoint', '').strip(),
Expand Down
24 changes: 23 additions & 1 deletion application/single_app/route_frontend_chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,14 @@ def _filter_chat_model_endpoints_by_governance(user_id, endpoints, feature_key):
return allowed_endpoints


def _build_initial_chat_model_selection(*, chat_model_options, preferred_model_id=None, preferred_model_deployment=None):
def _build_initial_chat_model_selection(
*,
chat_model_options,
preferred_model_id=None,
preferred_model_deployment=None,
default_model_selection=None,
use_admin_default=False,
):
scope_order = {
'global': 0,
'personal': 1,
Expand Down Expand Up @@ -574,6 +581,16 @@ def sort_key(option):
normalized_preferred_model_id = _normalize_chat_model_value(preferred_model_id)
normalized_preferred_model_deployment = _normalize_chat_model_value(preferred_model_deployment)

if use_admin_default and isinstance(default_model_selection, dict):
default_endpoint_id = _normalize_chat_model_value(default_model_selection.get('endpoint_id'))
default_model_id = _normalize_chat_model_value(default_model_selection.get('model_id'))
if default_endpoint_id and default_model_id:
for option in sorted_options:
endpoint_id = _normalize_chat_model_value(option.get('endpoint_id'))
model_id = _normalize_chat_model_value(option.get('model_id'))
if endpoint_id == default_endpoint_id and model_id == default_model_id:
return serialize_option(option)

if normalized_preferred_model_id:
for option in sorted_options:
selection_key = _normalize_chat_model_value(option.get('selection_key'))
Expand Down Expand Up @@ -967,6 +984,11 @@ def chats():
chat_model_options=chat_model_options,
preferred_model_id=user_settings_dict.get('preferredModelId'),
preferred_model_deployment=user_settings_dict.get('preferredModelDeployment'),
default_model_selection=settings.get('default_model_selection', {}),
use_admin_default=bool(
enable_multi_model_endpoints
and settings.get('enable_default_model_for_new_conversations', False)
),
)

return render_template(
Expand Down
14 changes: 12 additions & 2 deletions application/single_app/static/js/chat/chat-conversations.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,14 @@ async function refreshModelSelection() {
try {
const settings = await loadUserSettings();
const modelSelectorModule = await import('./chat-model-selector.js');
const useAdminDefault = Boolean(
window.adminNewConversationDefaultsActive
&& window.appSettings?.enable_default_model_for_new_conversations
&& window.initialChatModelSelection?.selection_key
);
await modelSelectorModule.populateModelDropdown({
preferredModelId: settings?.preferredModelId,
preferredModelDeployment: settings?.preferredModelDeployment,
preferredModelId: useAdminDefault ? window.initialChatModelSelection.selection_key : settings?.preferredModelId,
preferredModelDeployment: useAdminDefault ? null : settings?.preferredModelDeployment,
preserveCurrentSelection: false,
});
} catch (error) {
Expand Down Expand Up @@ -1517,6 +1522,7 @@ export function addConversationToList(conversationId, title = null, classificati
export async function selectConversation(conversationId) {
currentConversationId = conversationId;
window.currentConversationId = conversationId;
window.adminNewConversationDefaultsActive = false;
notifyConversationContextChanged("select", conversationId);

const convoItem = document.querySelector(`.conversation-item[data-conversation-id="${conversationId}"]`);
Expand Down Expand Up @@ -2219,6 +2225,10 @@ export async function createNewConversation(callback, options = {}) {
}

currentConversationId = data.conversation_id;
window.adminNewConversationDefaultsActive = Boolean(
window.appSettings?.enable_multi_model_endpoints
&& window.appSettings?.enable_default_model_for_new_conversations
);
// Reset scope lock for new conversation
resetScopeLock({ preserveSelections });
// Add to list (pass empty classifications for new convo)
Expand Down
41 changes: 40 additions & 1 deletion application/single_app/static/js/chat/chat-messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { autoplayTTSIfEnabled, isTTSAutoplayEnabled, playTTS } from "./chat-tts.
import { saveUserSetting } from "./chat-layout.js";
import { sendMessageWithStreaming } from "./chat-streaming.js";
import { getCurrentReasoningEffort, isReasoningEffortEnabled } from './chat-reasoning.js';
import {
restoreAdminDefaultModelSelection,
restoreModelSelectionFromConversationMetadata,
} from './chat-model-selector.js';
import { areAgentsEnabled } from './chat-agents.js';
import { createThoughtsToggleHtml, attachThoughtsToggleListener } from './chat-thoughts.js';
import { destroyInlineCharts, extractInlineChartBlocks, hydrateInlineCharts, injectInlineChartHtml, restoreInlineChartTokens } from './chat-inline-charts.js';
Expand Down Expand Up @@ -2128,6 +2132,21 @@ export function groupGeneratedImageProposalMessages(messages = []) {
return groupedMessages;
}

function getLastConversationModelSelection(messages = []) {
if (!Array.isArray(messages)) {
return null;
}

for (let index = messages.length - 1; index >= 0; index -= 1) {
const modelSelection = messages[index]?.metadata?.model_selection;
if (modelSelection && typeof modelSelection === 'object') {
return modelSelection;
}
}

return null;
}

export function loadMessages(conversationId) {
window.SimpleChatM365PendingActions?.setConversation(conversationId);
// Clear search highlights when loading a different conversation
Expand Down Expand Up @@ -2159,6 +2178,19 @@ export function loadMessages(conversationId) {
console.log(`--- Loading messages for ${conversationId} ---`);
updateConversationTaskDocumentsFromMessages(Array.isArray(data.messages) ? data.messages : [], conversationId);
updateComparisonChatUploadCatalog(Array.isArray(data.messages) ? data.messages : []);
const lastModelSelection = getLastConversationModelSelection(data.messages);
if (lastModelSelection) {
window.adminNewConversationDefaultsActive = false;
restoreModelSelectionFromConversationMetadata(lastModelSelection);
} else if ((Array.isArray(data.messages) ? data.messages : []).length === 0) {
window.adminNewConversationDefaultsActive = Boolean(
window.appSettings?.enable_multi_model_endpoints
&& window.appSettings?.enable_default_model_for_new_conversations
);
restoreAdminDefaultModelSelection();
} else {
window.adminNewConversationDefaultsActive = false;
}
const generatedImageProposalMessages = groupGeneratedImageProposalMessages(data.messages);
const assistantMessageIds = new Set(
(Array.isArray(data.messages) ? data.messages : [])
Expand Down Expand Up @@ -8669,7 +8701,13 @@ document.addEventListener('DOMContentLoaded', function() {

// Save the selected model when it changes
if (modelSelect) {
modelSelect.addEventListener("change", function() {
modelSelect.addEventListener("change", function(event) {
const userInitiated = event.isTrusted || event.detail?.userInitiated === true;
if (userInitiated) {
window.adminNewConversationDefaultsActive = false;
} else {
return;
}
const selectedModel = modelSelect.value;
if (window.appSettings?.enable_multi_model_endpoints) {
const selectedOption = modelSelect.options[modelSelect.selectedIndex];
Expand All @@ -8680,6 +8718,7 @@ if (modelSelect) {
console.log(`Saving preferred model deployment: ${selectedModel}`);
saveUserSetting({ preferredModelDeployment: selectedModel });
}

});
}

Expand Down
55 changes: 55 additions & 0 deletions application/single_app/static/js/chat/chat-model-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,4 +610,59 @@ export function refreshModelSelector() {
}

modelSelectorController?.refresh();
}

function findModelOptionForMetadata(modelSelection = {}) {
if (!modelSelect || !modelSelection || typeof modelSelection !== 'object') {
return null;
}

const endpointId = String(modelSelection.model_endpoint_id || modelSelection.endpoint_id || '').trim();
const modelId = String(modelSelection.model_id || '').trim();
const selectedModel = String(modelSelection.selected_model || modelSelection.request_model || '').trim();
const frontendModel = String(modelSelection.frontend_requested_model || modelSelection.deployment_name || '').trim();
const options = Array.from(modelSelect.options).filter(option => !option.disabled);

if (endpointId && modelId) {
const endpointMatch = options.find(option => (
option.dataset.endpointId === endpointId
&& option.dataset.modelId === modelId
));
if (endpointMatch) {
return endpointMatch;
}
}

const modelNames = [frontendModel, selectedModel].filter(Boolean);
return options.find(option => modelNames.some(modelName => (
option.dataset.requestModel === modelName
|| option.dataset.deploymentName === modelName
|| option.value === modelName
))) || null;
}

export function restoreModelSelectionFromConversationMetadata(modelSelection = {}) {
const matchingOption = findModelOptionForMetadata(modelSelection);
if (!matchingOption || !modelSelect) {
return false;
}

modelSelect.value = matchingOption.value;
matchingOption.selected = true;
modelSelectorController?.refresh();
modelSelect.dispatchEvent(new CustomEvent('change', {
bubbles: true,
detail: { conversationRestore: true }
}));
return true;
}

export function restoreAdminDefaultModelSelection() {
if (!window.appSettings?.enable_default_model_for_new_conversations) {
return false;
}

return restoreModelSelectionFromConversationMetadata(
window.initialChatModelSelection || window.appSettings.default_model_selection || {}
);
}
Loading
Loading