-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Reduce amount of stuff we fetch on /persona
#4988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0e53f34
3176c67
5baff11
cb0c9b3
1e50a4a
fad732f
3ee5653
47ea4a0
7cc6845
ed6c756
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from collections.abc import Sequence | ||
from datetime import datetime | ||
from enum import Enum | ||
from uuid import UUID | ||
|
||
from fastapi import HTTPException | ||
|
@@ -11,7 +12,6 @@ | |
from sqlalchemy import update | ||
from sqlalchemy.orm import aliased | ||
from sqlalchemy.orm import joinedload | ||
from sqlalchemy.orm import selectinload | ||
from sqlalchemy.orm import Session | ||
|
||
from onyx.auth.schemas import UserRole | ||
|
@@ -22,6 +22,7 @@ | |
from onyx.configs.constants import NotificationType | ||
from onyx.context.search.enums import RecencyBiasSetting | ||
from onyx.db.constants import SLACK_BOT_PERSONA_PREFIX | ||
from onyx.db.models import ConnectorCredentialPair | ||
from onyx.db.models import DocumentSet | ||
from onyx.db.models import Persona | ||
from onyx.db.models import Persona__User | ||
|
@@ -45,6 +46,12 @@ | |
logger = setup_logger() | ||
|
||
|
||
class PersonaLoadType(Enum): | ||
NONE = "none" | ||
MINIMAL = "minimal" | ||
FULL = "full" | ||
|
||
|
||
def _add_user_filters( | ||
stmt: Select, user: User | None, get_editable: bool = True | ||
) -> Select: | ||
|
@@ -322,16 +329,15 @@ def update_persona_public_status( | |
|
||
|
||
def get_personas_for_user( | ||
# defines how much of the persona to pre-load | ||
load_type: PersonaLoadType, | ||
# if user is `None` assume the user is an admin or auth is disabled | ||
user: User | None, | ||
db_session: Session, | ||
get_editable: bool = True, | ||
include_default: bool = True, | ||
include_slack_bot_personas: bool = False, | ||
include_deleted: bool = False, | ||
joinedload_all: bool = False, | ||
# a bit jank | ||
include_prompt: bool = True, | ||
) -> Sequence[Persona]: | ||
stmt = select(Persona) | ||
stmt = _add_user_filters(stmt, user, get_editable) | ||
|
@@ -343,20 +349,45 @@ def get_personas_for_user( | |
if not include_deleted: | ||
stmt = stmt.where(Persona.deleted.is_(False)) | ||
|
||
if joinedload_all: | ||
if load_type == PersonaLoadType.MINIMAL: | ||
# For ChatPage, only load essential relationships | ||
stmt = stmt.options( | ||
# Used for retrieval capability checking | ||
joinedload(Persona.tools), | ||
# Used for filtering | ||
joinedload(Persona.labels), | ||
# only show document sets in the UI that the assistant has access to | ||
joinedload(Persona.document_sets), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably should include these from below since they get used in the minimalsnapshot:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also these:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also on user |
||
joinedload(Persona.document_sets) | ||
.joinedload(DocumentSet.connector_credential_pairs) | ||
.joinedload(ConnectorCredentialPair.connector), | ||
joinedload(Persona.document_sets) | ||
.joinedload(DocumentSet.connector_credential_pairs) | ||
.joinedload(ConnectorCredentialPair.credential), | ||
# user | ||
joinedload(Persona.user), | ||
) | ||
elif load_type == PersonaLoadType.FULL: | ||
stmt = stmt.options( | ||
selectinload(Persona.tools), | ||
selectinload(Persona.document_sets), | ||
selectinload(Persona.groups), | ||
selectinload(Persona.users), | ||
selectinload(Persona.labels), | ||
selectinload(Persona.user_files), | ||
selectinload(Persona.user_folders), | ||
joinedload(Persona.user), | ||
joinedload(Persona.tools), | ||
joinedload(Persona.document_sets) | ||
.joinedload(DocumentSet.connector_credential_pairs) | ||
.joinedload(ConnectorCredentialPair.connector), | ||
joinedload(Persona.document_sets) | ||
.joinedload(DocumentSet.connector_credential_pairs) | ||
.joinedload(ConnectorCredentialPair.credential), | ||
joinedload(Persona.document_sets).joinedload(DocumentSet.users), | ||
joinedload(Persona.document_sets).joinedload(DocumentSet.groups), | ||
joinedload(Persona.groups), | ||
joinedload(Persona.users), | ||
joinedload(Persona.labels), | ||
joinedload(Persona.user_files), | ||
joinedload(Persona.user_folders), | ||
joinedload(Persona.prompts), | ||
) | ||
if include_prompt: | ||
stmt = stmt.options(selectinload(Persona.prompts)) | ||
|
||
results = db_session.execute(stmt).scalars().all() | ||
results = db_session.execute(stmt).unique().scalars().all() | ||
return results | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
from onyx.db.persona import get_personas_for_user | ||
from onyx.db.persona import mark_persona_as_deleted | ||
from onyx.db.persona import mark_persona_as_not_deleted | ||
from onyx.db.persona import PersonaLoadType | ||
from onyx.db.persona import update_all_personas_display_priority | ||
from onyx.db.persona import update_persona_is_default | ||
from onyx.db.persona import update_persona_label | ||
|
@@ -45,6 +46,7 @@ | |
from onyx.server.features.persona.models import FullPersonaSnapshot | ||
from onyx.server.features.persona.models import GenerateStarterMessageRequest | ||
from onyx.server.features.persona.models import ImageGenerationToolStatus | ||
from onyx.server.features.persona.models import MinimalPersonaSnapshot | ||
from onyx.server.features.persona.models import PersonaLabelCreate | ||
from onyx.server.features.persona.models import PersonaLabelResponse | ||
from onyx.server.features.persona.models import PersonaSharedNotificationData | ||
|
@@ -154,7 +156,7 @@ def list_personas_admin( | |
user=user, | ||
get_editable=get_editable, | ||
include_deleted=include_deleted, | ||
joinedload_all=True, | ||
load_type=PersonaLoadType.FULL, | ||
) | ||
] | ||
|
||
|
@@ -393,14 +395,13 @@ def list_personas( | |
db_session: Session = Depends(get_session), | ||
include_deleted: bool = False, | ||
persona_ids: list[int] = Query(None), | ||
) -> list[PersonaSnapshot]: | ||
) -> list[MinimalPersonaSnapshot]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Include return type details in docstring. This is a breaking API change since the return type changed from PersonaSnapshot to MinimalPersonaSnapshot. |
||
personas = get_personas_for_user( | ||
load_type=PersonaLoadType.MINIMAL, | ||
user=user, | ||
include_deleted=include_deleted, | ||
db_session=db_session, | ||
get_editable=False, | ||
joinedload_all=True, | ||
include_prompt=False, | ||
) | ||
|
||
if persona_ids: | ||
|
@@ -416,7 +417,8 @@ def list_personas( | |
) | ||
] | ||
|
||
return [PersonaSnapshot.from_model(p) for p in personas] | ||
result = [MinimalPersonaSnapshot.from_model(p) for p in personas] | ||
return result | ||
|
||
|
||
@basic_router.get("/{persona_id}") | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -18,6 +18,64 @@ | |||||||||||||||||||||
logger = setup_logger() | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
class MinimalPersonaSnapshot(BaseModel): | ||||||||||||||||||||||
"""Minimal persona model optimized for ChatPage.tsx - only includes fields actually used""" | ||||||||||||||||||||||
|
||||||||||||||||||||||
# Core fields used by ChatPage | ||||||||||||||||||||||
id: int | ||||||||||||||||||||||
name: str | ||||||||||||||||||||||
description: str | ||||||||||||||||||||||
tools: list[ToolSnapshot] | ||||||||||||||||||||||
starter_messages: list[StarterMessage] | None | ||||||||||||||||||||||
document_sets: list[DocumentSet] | ||||||||||||||||||||||
llm_model_version_override: str | None | ||||||||||||||||||||||
llm_model_provider_override: str | None | ||||||||||||||||||||||
|
||||||||||||||||||||||
uploaded_image_id: str | None | ||||||||||||||||||||||
icon_shape: int | None | ||||||||||||||||||||||
icon_color: str | None | ||||||||||||||||||||||
|
||||||||||||||||||||||
is_public: bool | ||||||||||||||||||||||
is_visible: bool | ||||||||||||||||||||||
display_priority: int | None | ||||||||||||||||||||||
is_default_persona: bool | ||||||||||||||||||||||
builtin_persona: bool | ||||||||||||||||||||||
|
||||||||||||||||||||||
labels: list["PersonaLabelSnapshot"] | ||||||||||||||||||||||
owner: MinimalUserSnapshot | None | ||||||||||||||||||||||
|
||||||||||||||||||||||
@classmethod | ||||||||||||||||||||||
def from_model(cls, persona: Persona) -> "MinimalPersonaSnapshot": | ||||||||||||||||||||||
return MinimalPersonaSnapshot( | ||||||||||||||||||||||
# Core fields actually used by ChatPage | ||||||||||||||||||||||
id=persona.id, | ||||||||||||||||||||||
name=persona.name, | ||||||||||||||||||||||
description=persona.description, | ||||||||||||||||||||||
tools=[ToolSnapshot.from_model(tool) for tool in persona.tools], | ||||||||||||||||||||||
starter_messages=persona.starter_messages, | ||||||||||||||||||||||
document_sets=[ | ||||||||||||||||||||||
DocumentSet.from_model(document_set) | ||||||||||||||||||||||
for document_set in persona.document_sets | ||||||||||||||||||||||
], | ||||||||||||||||||||||
llm_model_version_override=persona.llm_model_version_override, | ||||||||||||||||||||||
llm_model_provider_override=persona.llm_model_provider_override, | ||||||||||||||||||||||
uploaded_image_id=persona.uploaded_image_id, | ||||||||||||||||||||||
icon_shape=persona.icon_shape, | ||||||||||||||||||||||
icon_color=persona.icon_color, | ||||||||||||||||||||||
is_public=persona.is_public, | ||||||||||||||||||||||
is_visible=persona.is_visible, | ||||||||||||||||||||||
display_priority=persona.display_priority, | ||||||||||||||||||||||
is_default_persona=persona.is_default_persona, | ||||||||||||||||||||||
builtin_persona=persona.builtin_persona, | ||||||||||||||||||||||
labels=[PersonaLabelSnapshot.from_model(label) for label in persona.labels], | ||||||||||||||||||||||
owner=( | ||||||||||||||||||||||
MinimalUserSnapshot(id=persona.user.id, email=persona.user.email) | ||||||||||||||||||||||
if persona.user | ||||||||||||||||||||||
else None | ||||||||||||||||||||||
), | ||||||||||||||||||||||
Comment on lines
+71
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: persona.user is accessed before the null check. Move the persona.user.id and email access inside the parentheses to prevent potential NullPointerException.
Suggested change
|
||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
class PromptSnapshot(BaseModel): | ||||||||||||||||||||||
id: int | ||||||||||||||||||||||
name: str | ||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -17,7 +17,6 @@ import { | |||||||||||||||||||||
import { FiEdit2 } from "react-icons/fi"; | ||||||||||||||||||||||
import { TrashIcon } from "@/components/icons/icons"; | ||||||||||||||||||||||
import { useUser } from "@/components/user/UserProvider"; | ||||||||||||||||||||||
import { useAssistants } from "@/components/context/AssistantsContext"; | ||||||||||||||||||||||
import { ConfirmEntityModal } from "@/components/modals/ConfirmEntityModal"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
function PersonaTypeDisplay({ persona }: { persona: Persona }) { | ||||||||||||||||||||||
|
@@ -40,16 +39,18 @@ function PersonaTypeDisplay({ persona }: { persona: Persona }) { | |||||||||||||||||||||
return <Text>Personal {persona.owner && <>({persona.owner.email})</>}</Text>; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
export function PersonasTable() { | ||||||||||||||||||||||
export function PersonasTable({ | ||||||||||||||||||||||
personas, | ||||||||||||||||||||||
refreshPersonas, | ||||||||||||||||||||||
}: { | ||||||||||||||||||||||
personas: Persona[]; | ||||||||||||||||||||||
refreshPersonas: () => void; | ||||||||||||||||||||||
}) { | ||||||||||||||||||||||
const router = useRouter(); | ||||||||||||||||||||||
const { popup, setPopup } = usePopup(); | ||||||||||||||||||||||
const { refreshUser, isAdmin } = useUser(); | ||||||||||||||||||||||
const { | ||||||||||||||||||||||
allAssistants: assistants, | ||||||||||||||||||||||
refreshAssistants, | ||||||||||||||||||||||
editablePersonas, | ||||||||||||||||||||||
} = useAssistants(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const editablePersonas = personas.filter((p) => !p.builtin_persona); | ||||||||||||||||||||||
const editablePersonaIds = useMemo(() => { | ||||||||||||||||||||||
return new Set(editablePersonas.map((p) => p.id.toString())); | ||||||||||||||||||||||
}, [editablePersonas]); | ||||||||||||||||||||||
|
@@ -63,18 +64,18 @@ export function PersonasTable() { | |||||||||||||||||||||
|
||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||
const editable = editablePersonas.sort(personaComparator); | ||||||||||||||||||||||
const nonEditable = assistants | ||||||||||||||||||||||
const nonEditable = personas | ||||||||||||||||||||||
.filter((p) => !editablePersonaIds.has(p.id.toString())) | ||||||||||||||||||||||
.sort(personaComparator); | ||||||||||||||||||||||
setFinalPersonas([...editable, ...nonEditable]); | ||||||||||||||||||||||
}, [editablePersonas, assistants, editablePersonaIds]); | ||||||||||||||||||||||
}, [editablePersonas, personas, editablePersonaIds]); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const updatePersonaOrder = async (orderedPersonaIds: UniqueIdentifier[]) => { | ||||||||||||||||||||||
const reorderedAssistants = orderedPersonaIds.map( | ||||||||||||||||||||||
(id) => assistants.find((assistant) => assistant.id.toString() === id)! | ||||||||||||||||||||||
const reorderedPersonas = orderedPersonaIds.map( | ||||||||||||||||||||||
(id) => personas.find((persona) => persona.id.toString() === id)! | ||||||||||||||||||||||
); | ||||||||||||||||||||||
Comment on lines
+74
to
76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Add null check here. The
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
setFinalPersonas(reorderedAssistants); | ||||||||||||||||||||||
setFinalPersonas(reorderedPersonas); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const displayPriorityMap = new Map<UniqueIdentifier, number>(); | ||||||||||||||||||||||
orderedPersonaIds.forEach((personaId, ind) => { | ||||||||||||||||||||||
|
@@ -96,12 +97,12 @@ export function PersonasTable() { | |||||||||||||||||||||
type: "error", | ||||||||||||||||||||||
message: `Failed to update persona order - ${await response.text()}`, | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
setFinalPersonas(assistants); | ||||||||||||||||||||||
await refreshAssistants(); | ||||||||||||||||||||||
setFinalPersonas(personas); | ||||||||||||||||||||||
await refreshPersonas(); | ||||||||||||||||||||||
return; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
await refreshAssistants(); | ||||||||||||||||||||||
await refreshPersonas(); | ||||||||||||||||||||||
await refreshUser(); | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
@@ -119,7 +120,7 @@ export function PersonasTable() { | |||||||||||||||||||||
if (personaToDelete) { | ||||||||||||||||||||||
const response = await deletePersona(personaToDelete.id); | ||||||||||||||||||||||
if (response.ok) { | ||||||||||||||||||||||
await refreshAssistants(); | ||||||||||||||||||||||
refreshPersonas(); | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Missing await keyword - refreshPersonas should be awaited before closing modal to ensure UI consistency
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. potential greptile w? |
||||||||||||||||||||||
closeDeleteModal(); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
setPopup({ | ||||||||||||||||||||||
|
@@ -147,7 +148,7 @@ export function PersonasTable() { | |||||||||||||||||||||
personaToToggleDefault.is_default_persona | ||||||||||||||||||||||
); | ||||||||||||||||||||||
if (response.ok) { | ||||||||||||||||||||||
await refreshAssistants(); | ||||||||||||||||||||||
refreshPersonas(); | ||||||||||||||||||||||
closeDefaultModal(); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
setPopup({ | ||||||||||||||||||||||
|
@@ -267,7 +268,7 @@ export function PersonasTable() { | |||||||||||||||||||||
persona.is_visible | ||||||||||||||||||||||
); | ||||||||||||||||||||||
if (response.ok) { | ||||||||||||||||||||||
await refreshAssistants(); | ||||||||||||||||||||||
refreshPersonas(); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
setPopup({ | ||||||||||||||||||||||
type: "error", | ||||||||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.