Scope auxiliary LLM endpoints by owner (#2996)

* fix(auth): scope auxiliary llm endpoints by owner

* fix(auth): scope auxiliary llm fallbacks by owner
This commit is contained in:
Vykos
2026-06-07 14:47:44 +02:00
committed by GitHub
parent 12a7e741d0
commit 83b0ab7cd3
11 changed files with 146 additions and 32 deletions
+1 -1
View File
@@ -551,7 +551,7 @@ async def build_chat_context(
# Auto-compact
messages, context_length, was_compacted = await maybe_compact(
sess, sess.endpoint_url, sess.model, messages, sess.headers,
sess, sess.endpoint_url, sess.model, messages, sess.headers, owner=user,
)
messages = trim_for_context(messages, context_length)
+12 -11
View File
@@ -39,11 +39,13 @@ def _first_chat_model(models) -> str:
def _resolve_research_endpoint(sess) -> tuple:
"""Return (endpoint_url, model, headers) for Deep Research, checking admin overrides."""
owner = getattr(sess, "owner", None) or None
url, model, headers = resolve_endpoint(
"research",
fallback_url=sess.endpoint_url,
fallback_model=sess.model,
fallback_headers=sess.headers,
owner=owner,
)
return url, model, headers
@@ -392,17 +394,17 @@ def setup_research_routes(research_handler, session_manager=None) -> APIRouter:
finally:
db.close()
else:
ep_url, ep_model, ep_headers = resolve_endpoint("research")
ep_url, ep_model, ep_headers = resolve_endpoint("research", owner=user)
if not ep_url:
ep_url, ep_model, ep_headers = resolve_endpoint("utility")
ep_url, ep_model, ep_headers = resolve_endpoint("utility", owner=user)
# When neither research nor utility is configured, use the user's
# configured DEFAULT model (default_endpoint_id/default_model) rather
# than arbitrarily grabbing the first enabled endpoint's first model
# (which surfaced gpt-3.5). "Default" should mean the default model.
if not ep_url:
ep_url, ep_model, ep_headers = resolve_endpoint("default")
ep_url, ep_model, ep_headers = resolve_endpoint("default", owner=user)
if not ep_url:
ep_url, ep_model, ep_headers = resolve_endpoint("chat")
ep_url, ep_model, ep_headers = resolve_endpoint("chat", owner=user)
if not ep_url:
from src.database import SessionLocal
from src.endpoint_resolver import normalize_base, build_chat_url, build_headers
@@ -572,19 +574,18 @@ def setup_research_routes(research_handler, session_manager=None) -> APIRouter:
ep_headers = dict(r_headers)
if not ep_url or not ep_model:
_merge(*resolve_endpoint("chat"))
_merge(*resolve_endpoint("chat", owner=user))
if not ep_url or not ep_model:
_merge(*resolve_endpoint("research"))
_merge(*resolve_endpoint("research", owner=user))
if not ep_url or not ep_model:
_merge(*resolve_endpoint("utility"))
_merge(*resolve_endpoint("utility", owner=user))
if not ep_url or not ep_model:
# Last resort: any enabled endpoint
# Last resort: this user's enabled endpoint, plus legacy shared rows.
from src.database import SessionLocal
from src.database import ModelEndpoint
from src.endpoint_resolver import normalize_base, build_chat_url, build_headers
db = SessionLocal()
try:
ep = db.query(ModelEndpoint).filter(ModelEndpoint.is_enabled == True).first()
ep = _owned_enabled_endpoint(db, user)
if ep:
base = normalize_base(ep.base_url)
fallback_url = build_chat_url(base)
@@ -594,7 +595,7 @@ def setup_research_routes(research_handler, session_manager=None) -> APIRouter:
try:
models = json.loads(ep.cached_models)
if models:
fallback_model = models[0]
fallback_model = _first_chat_model(models)
except Exception:
pass
_merge(fallback_url, fallback_model, fallback_headers)
+2 -1
View File
@@ -924,7 +924,8 @@ def setup_session_routes(session_manager: SessionManager, config: dict, webhook_
from src.endpoint_resolver import resolve_endpoint
from src.llm_core import llm_call_async
url, model, headers = resolve_endpoint("utility", owner=get_current_user(request))
owner = getattr(session, "owner", None) or effective_user(request)
url, model, headers = resolve_endpoint("utility", owner=owner)
if not url or not model:
url, model, headers = session.endpoint_url, session.model, session.headers
if not url or not model:
+9 -4
View File
@@ -291,20 +291,24 @@ def setup_task_routes(task_scheduler) -> APIRouter:
def _owner(request: Request):
return get_current_user(request)
async def _generate_task_name(prompt: str) -> str:
async def _generate_task_name(prompt: str, owner: Optional[str] = None) -> str:
"""Use LLM to generate a short task name from the prompt."""
try:
from src.llm_core import llm_call_async
from core.database import Session as DbSession
db = SessionLocal()
try:
recent = db.query(DbSession).filter(
q = db.query(DbSession).filter(
DbSession.endpoint_url.isnot(None),
DbSession.model.isnot(None),
).order_by(DbSession.created_at.desc()).first()
)
if owner:
q = q.filter(DbSession.owner == owner)
recent = q.order_by(DbSession.created_at.desc()).first()
if not recent:
return prompt[:50].strip()
url, model = recent.endpoint_url, recent.model
headers = recent.headers or {}
finally:
db.close()
@@ -315,6 +319,7 @@ def setup_task_routes(task_scheduler) -> APIRouter:
{"role": "user", "content": prompt[:500]},
],
max_tokens=20,
headers=headers,
timeout=15,
)
title = result.strip().strip('"\'').strip()
@@ -479,7 +484,7 @@ def setup_task_routes(task_scheduler) -> APIRouter:
from src.builtin_actions import BUILTIN_ACTION_INFO
name = BUILTIN_ACTION_INFO.get(req.action, req.action or "Action Task")
elif req.prompt:
name = await _generate_task_name(req.prompt)
name = await _generate_task_name(req.prompt, owner=user)
else:
name = "Untitled Task"