Fix 'default' model selection breaking readiness indicator
When a user selected the 'Default' option in Settings, the dropdown sent an empty string "" to the backend. The route saved it as a DB row, which caused get_setting() to return "" instead of falling back to Config defaults. The chat status endpoint then tried to match "" against installed model names — always failing — resulting in model: "not_found" and a permanently failing readiness indicator. services/settings.py: - Add delete_setting() helper: removes a setting row so get_setting() correctly falls back to its hardcoded default argument routes/settings.py: - Import delete_setting - When default_model or intent_model are saved as empty string, delete the DB row instead of storing "" — cleanly restores Config fallback routes/chat.py: - chat_status_route: add explicit `or Config.OLLAMA_MODEL` guard for any existing "" rows written before this fix (migration safety net) - send_message and summarize routes: same guard on model resolution so empty settings never cause silent generation failures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -135,7 +135,7 @@ async def send_message_route(conv_id: int):
|
|||||||
if msg.role != "system":
|
if msg.role != "system":
|
||||||
history.append({"role": msg.role, "content": msg.content})
|
history.append({"role": msg.role, "content": msg.content})
|
||||||
|
|
||||||
model = conv.model or await get_setting(uid, "default_model", Config.OLLAMA_MODEL)
|
model = conv.model or await get_setting(uid, "default_model", Config.OLLAMA_MODEL) or Config.OLLAMA_MODEL
|
||||||
|
|
||||||
# Launch background generation task (context building happens inside the task)
|
# Launch background generation task (context building happens inside the task)
|
||||||
asyncio.create_task(run_generation(
|
asyncio.create_task(run_generation(
|
||||||
@@ -257,7 +257,7 @@ async def summarize_conversation_route(conv_id: int):
|
|||||||
conv = await get_conversation(uid, conv_id)
|
conv = await get_conversation(uid, conv_id)
|
||||||
if conv is None:
|
if conv is None:
|
||||||
return jsonify({"error": "Conversation not found"}), 404
|
return jsonify({"error": "Conversation not found"}), 404
|
||||||
model = conv.model or await get_setting(uid, "default_model", Config.OLLAMA_MODEL)
|
model = conv.model or await get_setting(uid, "default_model", Config.OLLAMA_MODEL) or Config.OLLAMA_MODEL
|
||||||
try:
|
try:
|
||||||
note = await summarize_conversation_as_note(uid, conv_id, model)
|
note = await summarize_conversation_as_note(uid, conv_id, model)
|
||||||
return jsonify(note), 201
|
return jsonify(note), 201
|
||||||
@@ -319,6 +319,9 @@ async def chat_status_route():
|
|||||||
"""Check Ollama availability, model installation, and model load state."""
|
"""Check Ollama availability, model installation, and model load state."""
|
||||||
uid = get_current_user_id()
|
uid = get_current_user_id()
|
||||||
default_model = await get_setting(uid, "default_model", Config.OLLAMA_MODEL)
|
default_model = await get_setting(uid, "default_model", Config.OLLAMA_MODEL)
|
||||||
|
# Guard against empty-string rows written by older code when user selected "default"
|
||||||
|
if not default_model:
|
||||||
|
default_model = Config.OLLAMA_MODEL
|
||||||
result = {
|
result = {
|
||||||
"ollama": "unavailable",
|
"ollama": "unavailable",
|
||||||
"model": "not_found",
|
"model": "not_found",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from fabledassistant.auth import login_required, get_current_user_id
|
|||||||
from fabledassistant.config import Config
|
from fabledassistant.config import Config
|
||||||
from fabledassistant.services.caldav import CALDAV_SETTING_KEYS, get_caldav_config, test_connection
|
from fabledassistant.services.caldav import CALDAV_SETTING_KEYS, get_caldav_config, test_connection
|
||||||
from fabledassistant.services.llm import get_installed_models
|
from fabledassistant.services.llm import get_installed_models
|
||||||
from fabledassistant.services.settings import get_all_settings, set_settings_batch
|
from fabledassistant.services.settings import delete_setting, get_all_settings, set_settings_batch
|
||||||
|
|
||||||
settings_bp = Blueprint("settings", __name__, url_prefix="/api/settings")
|
settings_bp = Blueprint("settings", __name__, url_prefix="/api/settings")
|
||||||
|
|
||||||
@@ -33,7 +33,20 @@ async def update_settings_route():
|
|||||||
if installed and model not in installed:
|
if installed and model not in installed:
|
||||||
return jsonify({"error": f"Model '{model}' is not installed"}), 400
|
return jsonify({"error": f"Model '{model}' is not installed"}), 400
|
||||||
|
|
||||||
await set_settings_batch(uid, {k: str(v) for k, v in data.items()})
|
# Empty string for model keys means "reset to system default".
|
||||||
|
# Delete the DB row so get_setting() falls back to Config defaults
|
||||||
|
# rather than returning "" and breaking model resolution everywhere.
|
||||||
|
_MODEL_KEYS = frozenset({"default_model", "intent_model"})
|
||||||
|
to_save = {}
|
||||||
|
for k, v in data.items():
|
||||||
|
str_v = str(v)
|
||||||
|
if k in _MODEL_KEYS and not str_v:
|
||||||
|
await delete_setting(uid, k)
|
||||||
|
else:
|
||||||
|
to_save[k] = str_v
|
||||||
|
|
||||||
|
if to_save:
|
||||||
|
await set_settings_batch(uid, to_save)
|
||||||
settings = await get_all_settings(uid)
|
settings = await get_all_settings(uid)
|
||||||
return jsonify(settings)
|
return jsonify(settings)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import delete as sa_delete, select
|
||||||
|
|
||||||
from fabledassistant.models import async_session
|
from fabledassistant.models import async_session
|
||||||
from fabledassistant.models.setting import Setting
|
from fabledassistant.models.setting import Setting
|
||||||
@@ -46,6 +46,15 @@ async def set_settings_batch(user_id: int, settings: dict[str, str]) -> None:
|
|||||||
logger.info("Batch-updated %d settings for user %d", len(settings), user_id)
|
logger.info("Batch-updated %d settings for user %d", len(settings), user_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_setting(user_id: int, key: str) -> None:
|
||||||
|
"""Remove a setting row so get_setting() returns its hardcoded default instead."""
|
||||||
|
async with async_session() as session:
|
||||||
|
await session.execute(
|
||||||
|
sa_delete(Setting).where(Setting.user_id == user_id, Setting.key == key)
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def get_all_settings(user_id: int) -> dict[str, str]:
|
async def get_all_settings(user_id: int) -> dict[str, str]:
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
|
|||||||
Reference in New Issue
Block a user