fix: adhere to system prompts

This commit is contained in:
cogwheel0
2025-09-20 18:28:12 +05:30
parent 093e8f3c0d
commit f73d308e06
2 changed files with 132 additions and 15 deletions

View File

@@ -483,6 +483,18 @@ class ApiService {
'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived', 'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived',
); );
String? systemPrompt;
final chatObject = chatData['chat'] as Map<String, dynamic>?;
if (chatObject != null) {
final systemValue = chatObject['system'];
if (systemValue is String && systemValue.trim().isNotEmpty) {
systemPrompt = systemValue;
}
} else if (chatData['system'] is String) {
final systemValue = (chatData['system'] as String).trim();
if (systemValue.isNotEmpty) systemPrompt = systemValue;
}
// For the list endpoint, we don't get the full chat messages // For the list endpoint, we don't get the full chat messages
// We'll need to fetch individual chats later if needed // We'll need to fetch individual chats later if needed
return Conversation( return Conversation(
@@ -490,6 +502,7 @@ class ApiService {
title: title, title: title,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
systemPrompt: systemPrompt,
pinned: pinned, pinned: pinned,
archived: archived, archived: archived,
shareId: shareId, shareId: shareId,
@@ -532,6 +545,16 @@ class ApiService {
// Parse messages from the 'chat' object or top-level messages // Parse messages from the 'chat' object or top-level messages
final chatObject = chatData['chat'] as Map<String, dynamic>?; final chatObject = chatData['chat'] as Map<String, dynamic>?;
String? systemPrompt;
if (chatObject != null) {
final systemValue = chatObject['system'];
if (systemValue is String && systemValue.trim().isNotEmpty) {
systemPrompt = systemValue;
}
} else if (chatData['system'] is String) {
final systemValue = (chatData['system'] as String).trim();
if (systemValue.isNotEmpty) systemPrompt = systemValue;
}
final messages = <ChatMessage>[]; final messages = <ChatMessage>[];
// Extract model from chat.models array // Extract model from chat.models array
@@ -663,6 +686,7 @@ class ApiService {
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
model: model, model: model,
systemPrompt: systemPrompt,
pinned: pinned, pinned: pinned,
archived: archived, archived: archived,
shareId: shareId, shareId: shareId,
@@ -1031,6 +1055,8 @@ class ApiService {
'id': '', 'id': '',
'title': title, 'title': title,
'models': model != null ? [model] : [], 'models': model != null ? [model] : [],
if (systemPrompt != null && systemPrompt.trim().isNotEmpty)
'system': systemPrompt,
'params': {}, 'params': {},
'history': { 'history': {
'messages': messagesMap, 'messages': messagesMap,
@@ -1155,6 +1181,8 @@ class ApiService {
'chat': { 'chat': {
if (title != null) 'title': title, // Include the title if provided if (title != null) 'title': title, // Include the title if provided
'models': model != null ? [model] : [], 'models': model != null ? [model] : [],
if (systemPrompt != null && systemPrompt.trim().isNotEmpty)
'system': systemPrompt,
'messages': messagesArray, 'messages': messagesArray,
'history': { 'history': {
'messages': messagesMap, 'messages': messagesMap,

View File

@@ -482,6 +482,7 @@ Future<String> _preseedAssistantAndPersist(
dynamic ref, { dynamic ref, {
String? existingAssistantId, String? existingAssistantId,
required String modelId, required String modelId,
String? systemPrompt,
}) async { }) async {
final api = ref.read(apiServiceProvider); final api = ref.read(apiServiceProvider);
final activeConv = ref.read(activeConversationProvider); final activeConv = ref.read(activeConversationProvider);
@@ -525,11 +526,16 @@ Future<String> _preseedAssistantAndPersist(
// Persist the skeleton to the server so the web client sees a correct chain // Persist the skeleton to the server so the web client sees a correct chain
try { try {
if (api != null && activeConv != null) { if (api != null && activeConv != null) {
final resolvedSystemPrompt = (systemPrompt != null &&
systemPrompt.trim().isNotEmpty)
? systemPrompt.trim()
: activeConv.systemPrompt;
final current = ref.read(chatMessagesProvider); final current = ref.read(chatMessagesProvider);
await api.updateConversationWithMessages( await api.updateConversationWithMessages(
activeConv.id, activeConv.id,
current, current,
model: modelId, model: modelId,
systemPrompt: resolvedSystemPrompt,
); );
} }
} catch (_) {} } catch (_) {}
@@ -711,7 +717,7 @@ Future<void> regenerateMessage(
throw Exception('No API service or model selected'); throw Exception('No API service or model selected');
} }
final activeConversation = ref.read(activeConversationProvider); var activeConversation = ref.read(activeConversationProvider);
if (activeConversation == null) { if (activeConversation == null) {
throw Exception('No active conversation'); throw Exception('No active conversation');
} }
@@ -753,6 +759,28 @@ Future<void> regenerateMessage(
// For real API, proceed with regeneration using existing conversation messages // For real API, proceed with regeneration using existing conversation messages
try { try {
Map<String, dynamic>? userSettingsData;
String? userSystemPrompt;
try {
userSettingsData = await api!.getUserSettings();
final systemValue = userSettingsData?['system'];
if (systemValue is String) {
final trimmed = systemValue.trim();
if (trimmed.isNotEmpty) {
userSystemPrompt = trimmed;
}
}
} catch (_) {}
if ((activeConversation.systemPrompt == null ||
activeConversation.systemPrompt!.trim().isEmpty) &&
(userSystemPrompt?.isNotEmpty ?? false)) {
final updated =
activeConversation.copyWith(systemPrompt: userSystemPrompt);
ref.read(activeConversationProvider.notifier).state = updated;
activeConversation = updated;
}
// Include selected tool ids so provider-native tool calling is triggered // Include selected tool ids so provider-native tool calling is triggered
final selectedToolIds = ref.read(selectedToolIdsProvider); final selectedToolIds = ref.read(selectedToolIdsProvider);
// Get conversation history for context (excluding the removed assistant message) // Get conversation history for context (excluding the removed assistant message)
@@ -787,10 +815,28 @@ Future<void> regenerateMessage(
} }
} }
final conversationSystemPrompt = activeConversation.systemPrompt?.trim();
final effectiveSystemPrompt = (conversationSystemPrompt != null &&
conversationSystemPrompt.isNotEmpty)
? conversationSystemPrompt
: userSystemPrompt;
if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) {
final hasSystemMessage = conversationMessages.any(
(m) => (m['role']?.toString().toLowerCase() ?? '') == 'system',
);
if (!hasSystemMessage) {
conversationMessages.insert(
0,
{'role': 'system', 'content': effectiveSystemPrompt},
);
}
}
// Pre-seed assistant skeleton and persist chain // Pre-seed assistant skeleton and persist chain
final String assistantMessageId = await _preseedAssistantAndPersist( final String assistantMessageId = await _preseedAssistantAndPersist(
ref, ref,
modelId: selectedModel.id, modelId: selectedModel.id,
systemPrompt: effectiveSystemPrompt,
); );
// Feature toggles // Feature toggles
@@ -912,14 +958,13 @@ Future<void> regenerateMessage(
// Resolve tool servers from user settings (if any) // Resolve tool servers from user settings (if any)
List<Map<String, dynamic>>? toolServers; List<Map<String, dynamic>>? toolServers;
try { final uiSettings = userSettingsData?['ui'] as Map<String, dynamic>?;
final userSettings = await api!.getUserSettings(); final rawServers = uiSettings != null ? (uiSettings['toolServers'] as List?) : null;
final ui = userSettings['ui'] as Map<String, dynamic>?; if (rawServers != null && rawServers.isNotEmpty) {
final rawServers = ui != null ? (ui['toolServers'] as List?) : null; try {
if (rawServers != null && rawServers.isNotEmpty) {
toolServers = await _resolveToolServers(rawServers, api); toolServers = await _resolveToolServers(rawServers, api);
} } catch (_) {}
} catch (_) {} }
// Background tasks parity with Web client (safe defaults) // Background tasks parity with Web client (safe defaults)
bool shouldGenerateTitle = false; bool shouldGenerateTitle = false;
@@ -1056,6 +1101,21 @@ Future<void> _sendMessageInternal(
throw Exception('No API service or model selected'); throw Exception('No API service or model selected');
} }
Map<String, dynamic>? userSettingsData;
String? userSystemPrompt;
if (!reviewerMode && api != null) {
try {
userSettingsData = await api.getUserSettings();
final systemValue = userSettingsData?['system'];
if (systemValue is String) {
final trimmed = systemValue.trim();
if (trimmed.isNotEmpty) {
userSystemPrompt = trimmed;
}
}
} catch (_) {}
}
// Check if we need to create a new conversation first // Check if we need to create a new conversation first
var activeConversation = ref.read(activeConversationProvider); var activeConversation = ref.read(activeConversationProvider);
@@ -1077,6 +1137,7 @@ Future<void> _sendMessageInternal(
title: 'New Chat', title: 'New Chat',
createdAt: DateTime.now(), createdAt: DateTime.now(),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
systemPrompt: userSystemPrompt,
messages: [userMessage], // Include the user message messages: [userMessage], // Include the user message
); );
@@ -1091,9 +1152,11 @@ Future<void> _sendMessageInternal(
title: 'New Chat', title: 'New Chat',
messages: [userMessage], // Include the first message in creation messages: [userMessage], // Include the first message in creation
model: selectedModel.id, model: selectedModel.id,
systemPrompt: userSystemPrompt,
); );
final updatedConversation = localConversation.copyWith( final updatedConversation = localConversation.copyWith(
id: serverConversation.id, id: serverConversation.id,
systemPrompt: serverConversation.systemPrompt ?? userSystemPrompt,
messages: serverConversation.messages.isNotEmpty messages: serverConversation.messages.isNotEmpty
? serverConversation.messages ? serverConversation.messages
: [userMessage], : [userMessage],
@@ -1131,6 +1194,15 @@ Future<void> _sendMessageInternal(
ref.read(chatMessagesProvider.notifier).addMessage(userMessage); ref.read(chatMessagesProvider.notifier).addMessage(userMessage);
} }
if (activeConversation != null &&
(activeConversation.systemPrompt == null ||
activeConversation.systemPrompt!.trim().isEmpty) &&
(userSystemPrompt?.isNotEmpty ?? false)) {
final updated = activeConversation.copyWith(systemPrompt: userSystemPrompt);
ref.read(activeConversationProvider.notifier).state = updated;
activeConversation = updated;
}
// We'll add the assistant message placeholder after we get the message ID from the API (or immediately in reviewer mode) // We'll add the assistant message placeholder after we get the message ID from the API (or immediately in reviewer mode)
// Immediately trigger title generation after user message is sent (first turn only) // Immediately trigger title generation after user message is sent (first turn only)
@@ -1234,6 +1306,23 @@ Future<void> _sendMessageInternal(
} }
} }
final conversationSystemPrompt = activeConversation?.systemPrompt?.trim();
final effectiveSystemPrompt = (conversationSystemPrompt != null &&
conversationSystemPrompt.isNotEmpty)
? conversationSystemPrompt
: userSystemPrompt;
if (effectiveSystemPrompt != null && effectiveSystemPrompt.isNotEmpty) {
final hasSystemMessage = conversationMessages.any(
(m) => (m['role']?.toString().toLowerCase() ?? '') == 'system',
);
if (!hasSystemMessage) {
conversationMessages.insert(
0,
{'role': 'system', 'content': effectiveSystemPrompt},
);
}
}
// Check feature toggles for API (gated by server availability) // Check feature toggles for API (gated by server availability)
final webSearchEnabled = final webSearchEnabled =
ref.read(webSearchEnabledProvider) && ref.read(webSearchEnabledProvider) &&
@@ -1270,6 +1359,7 @@ Future<void> _sendMessageInternal(
activeConvForSeed.id, activeConvForSeed.id,
msgsForSeed, msgsForSeed,
model: selectedModel.id, model: selectedModel.id,
systemPrompt: effectiveSystemPrompt,
); );
} }
} catch (_) {} } catch (_) {}
@@ -1378,14 +1468,13 @@ Future<void> _sendMessageInternal(
// Resolve tool servers from user settings (if any) // Resolve tool servers from user settings (if any)
List<Map<String, dynamic>>? toolServers; List<Map<String, dynamic>>? toolServers;
try { final uiSettings = userSettingsData?['ui'] as Map<String, dynamic>?;
final userSettings = await api.getUserSettings(); final rawServers = uiSettings != null ? (uiSettings['toolServers'] as List?) : null;
final ui = userSettings['ui'] as Map<String, dynamic>?; if (rawServers != null && rawServers.isNotEmpty) {
final rawServers = ui != null ? (ui['toolServers'] as List?) : null; try {
if (rawServers != null && rawServers.isNotEmpty) {
toolServers = await _resolveToolServers(rawServers, api); toolServers = await _resolveToolServers(rawServers, api);
} } catch (_) {}
} catch (_) {} }
// Background tasks parity with Web client (safe defaults) // Background tasks parity with Web client (safe defaults)
// Enable title/tags generation on the very first user turn of a new chat. // Enable title/tags generation on the very first user turn of a new chat.