mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-03 19:52:20 +01:00
fix(model-routing): keep selected models strict (#5801)
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexandre Teixeira
parent
53869d194d
commit
b52296471b
+11
-11
@@ -21,6 +21,7 @@ from src import agent_runs
|
||||
from src.model_context import estimate_tokens
|
||||
from src.chat_helpers import coerce_message_and_session
|
||||
from src.endpoint_resolver import normalize_base as _normalize_base, build_chat_url
|
||||
from src.foreground_model_routing import build_foreground_model_candidates
|
||||
from src.session_search import search_session_messages
|
||||
from src.prompt_security import untrusted_context_message
|
||||
from core.exceptions import SessionNotFoundError
|
||||
@@ -1399,14 +1400,14 @@ def setup_chat_routes(
|
||||
thinking_response = ""
|
||||
last_metrics = None
|
||||
|
||||
# Configured fallback chain for the default chat model. Tried in
|
||||
# order if the session's primary model fails before producing
|
||||
# output. Resolved once per request.
|
||||
try:
|
||||
from src.endpoint_resolver import resolve_chat_fallback_candidates
|
||||
_fallback_candidates = resolve_chat_fallback_candidates(owner=_user)
|
||||
except Exception:
|
||||
_fallback_candidates = []
|
||||
# Foreground Chat and Agent requests use one owner-aware policy
|
||||
# boundary. Legacy `default_model_fallbacks` data is not eligible.
|
||||
_foreground_candidates = build_foreground_model_candidates(
|
||||
sess.endpoint_url,
|
||||
sess.model,
|
||||
sess.headers,
|
||||
owner=_user,
|
||||
)
|
||||
|
||||
# Send model name early so the frontend can show it during streaming
|
||||
_model_suffix = "Research" if effective_do_research else None
|
||||
@@ -1522,9 +1523,8 @@ def setup_chat_routes(
|
||||
_actual_model = None
|
||||
# ── Chat mode: call stream_llm directly, NO tools, NO document access ──
|
||||
try:
|
||||
_chat_candidates = [(sess.endpoint_url, sess.model, sess.headers)] + _fallback_candidates
|
||||
async for chunk in stream_llm_with_fallback(
|
||||
_chat_candidates,
|
||||
_foreground_candidates,
|
||||
messages,
|
||||
temperature=ctx.preset.temperature,
|
||||
# Respect the preset; 0/unset = let the server decide (no
|
||||
@@ -1710,7 +1710,7 @@ def setup_chat_routes(
|
||||
disabled_tools=disabled_tools if disabled_tools else None,
|
||||
tool_policy=tool_policy,
|
||||
owner=_user,
|
||||
fallbacks=_fallback_candidates,
|
||||
fallbacks=_foreground_candidates[1:],
|
||||
plan_mode=plan_mode,
|
||||
approved_plan=approved_plan or None,
|
||||
workspace=workspace or None,
|
||||
|
||||
@@ -5327,9 +5327,9 @@ def setup_email_routes():
|
||||
# Build a candidate chain so a stale session-stored API key
|
||||
# (the most common cause of "authentication failed" here)
|
||||
# doesn't kill AI Reply outright — fall through to the
|
||||
# user's Utility / Default endpoints AND their configured
|
||||
# fallback chains. Dedupe by url+model so we don't retry
|
||||
# the same broken endpoint.
|
||||
# user's Utility / Default endpoints and the active Utility
|
||||
# fallback chain. The retired default-fallback hook stays empty.
|
||||
# Dedupe by url+model so we don't retry the same broken endpoint.
|
||||
from src.llm_core import llm_call_async_with_fallback
|
||||
from src.endpoint_resolver import (
|
||||
resolve_utility_fallback_candidates,
|
||||
@@ -5358,7 +5358,7 @@ def setup_email_routes():
|
||||
_add(_d_url, _d_model, _d_headers)
|
||||
except Exception:
|
||||
pass
|
||||
# Configured fallback chains last.
|
||||
# Active Utility fallbacks, then the retired default hook.
|
||||
for cand in resolve_utility_fallback_candidates(owner=owner) or []:
|
||||
_add(*cand)
|
||||
for cand in resolve_chat_fallback_candidates(owner=owner) or []:
|
||||
|
||||
+2
-32
@@ -46,10 +46,11 @@ _ENDPOINT_SETTING_FIELDS = {
|
||||
}
|
||||
|
||||
_ENDPOINT_FALLBACK_FIELDS = {
|
||||
"default_model_fallbacks": "Default Model Fallbacks",
|
||||
"utility_model_fallbacks": "Utility Model Fallbacks",
|
||||
"vision_model_fallbacks": "Vision Model Fallbacks",
|
||||
}
|
||||
# `default_model_fallbacks` is intentionally absent. The legacy data remains
|
||||
# stored as-is even when an endpoint is removed, but no longer affects routing.
|
||||
|
||||
|
||||
def _speech_settings_using_endpoint(settings: dict, ep_id: str) -> list:
|
||||
@@ -2437,7 +2438,6 @@ def setup_model_routes(model_discovery):
|
||||
_user_prefs = _load_for_user(_user) or {}
|
||||
ep_id = (_user_prefs.get("default_endpoint_id") or "").strip()
|
||||
model = (_user_prefs.get("default_model") or "").strip()
|
||||
_fallbacks = _user_prefs.get("default_model_fallbacks") or []
|
||||
# If user has no personal default, fall back to global default
|
||||
# But only based on the "share_defaults_with_users" flag
|
||||
# (only if share_defaults_with_users is enabled)
|
||||
@@ -2446,12 +2446,9 @@ def setup_model_routes(model_discovery):
|
||||
ep_id = settings.get("default_endpoint_id", "")
|
||||
if not model:
|
||||
model = settings.get("default_model", "")
|
||||
if not _fallbacks:
|
||||
_fallbacks = settings.get("default_model_fallbacks") or []
|
||||
else:
|
||||
ep_id = settings.get("default_endpoint_id", "")
|
||||
model = settings.get("default_model", "")
|
||||
_fallbacks = settings.get("default_model_fallbacks") or []
|
||||
db = SessionLocal()
|
||||
try:
|
||||
ep = None
|
||||
@@ -2466,33 +2463,6 @@ def setup_model_routes(model_discovery):
|
||||
if _user and not _is_admin:
|
||||
ep_q = owner_filter(ep_q, ModelEndpoint, _user)
|
||||
ep = ep_q.first()
|
||||
# Configured fallback chain — when the chosen default endpoint is
|
||||
# gone/disabled, honor the user's configured `default_model_fallbacks`
|
||||
# in order BEFORE arbitrarily grabbing the first enabled endpoint.
|
||||
# (Previously this jumped straight to "first enabled", which is why
|
||||
# deleting/changing the main endpoint silently reassigned the default
|
||||
# chat to some unrelated endpoint instead of the fallback.)
|
||||
if not ep:
|
||||
for entry in _fallbacks:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
fid = (entry.get("endpoint_id") or "").strip()
|
||||
if not fid:
|
||||
continue
|
||||
cand_q = db.query(ModelEndpoint).filter(
|
||||
ModelEndpoint.id == fid, ModelEndpoint.is_enabled == True
|
||||
)
|
||||
if _user and not _is_admin:
|
||||
cand_q = owner_filter(cand_q, ModelEndpoint, _user)
|
||||
cand = cand_q.first()
|
||||
if cand:
|
||||
ep = cand
|
||||
# Use the fallback entry's model. Reset even when empty
|
||||
# so we don't carry the prior endpoint's stale model onto
|
||||
# this fallback — the cached-models lookup below then
|
||||
# fills it from the fallback endpoint.
|
||||
model = (entry.get("model") or "").strip()
|
||||
break
|
||||
# Last resort: first enabled endpoint owned by THIS user. Do not
|
||||
# include null-owner/shared endpoints here: a brand-new user with
|
||||
# no explicit default should not auto-open a pending chat using an
|
||||
|
||||
Reference in New Issue
Block a user