Query history speed fix (#109)

This commit is contained in:
rashad-danswer 2024-06-20 18:35:28 -07:00 committed by Chris Weaver
parent 733d4e666b
commit 98a58337a7
5 changed files with 148 additions and 39 deletions

View File

@ -3,31 +3,49 @@ from typing import Literal
from sqlalchemy import asc
from sqlalchemy import desc
from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import Session
from danswer.db.models import ChatMessage
from danswer.db.models import ChatSession
SortByOptions = Literal["time_sent"]
def fetch_chat_sessions_by_time(
def fetch_chat_sessions_eagerly_by_time(
start: datetime.datetime,
end: datetime.datetime,
db_session: Session,
ascending: bool = False,
limit: int | None = 500,
) -> list[ChatSession]:
order = asc(ChatSession.time_created) if ascending else desc(ChatSession.time_created) # type: ignore
time_order = asc(ChatSession.time_created) if ascending else desc(ChatSession.time_created) # type: ignore
message_order = asc(ChatMessage.id) # type: ignore
subquery = (
db_session.query(ChatSession.id, ChatSession.time_created)
.filter(ChatSession.time_created.between(start, end))
.order_by(desc(ChatSession.id), time_order)
.distinct(ChatSession.id)
.limit(limit)
.subquery()
)
query = (
db_session.query(ChatSession)
.filter(ChatSession.time_created >= start, ChatSession.time_created <= end)
.order_by(order)
.join(subquery, ChatSession.id == subquery.c.id)
.outerjoin(ChatMessage, ChatSession.id == ChatMessage.chat_session_id)
.options(
joinedload(ChatSession.user),
joinedload(ChatSession.persona),
contains_eager(ChatSession.messages).joinedload(
ChatMessage.chat_message_feedbacks
),
)
.order_by(time_order, message_order)
)
if limit is not None:
query = query.limit(limit)
chat_sessions = query.all()
return chat_sessions

View File

@ -3,6 +3,7 @@ import io
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from typing import Literal
from fastapi import APIRouter
from fastapi import Depends
@ -21,7 +22,7 @@ from danswer.db.chat import get_chat_session_by_id
from danswer.db.engine import get_session
from danswer.db.models import ChatMessage
from danswer.db.models import ChatSession
from ee.danswer.db.query_history import fetch_chat_sessions_by_time
from ee.danswer.db.query_history import fetch_chat_sessions_eagerly_by_time
router = APIRouter()
@ -81,6 +82,17 @@ class MessageSnapshot(BaseModel):
)
class ChatSessionMinimal(BaseModel):
id: int
user_email: str
name: str | None
first_user_message: str
first_ai_message: str
persona_name: str
time_created: datetime
feedback_type: QAFeedbackType | Literal["mixed"] | None
class ChatSessionSnapshot(BaseModel):
id: int
user_email: str
@ -146,6 +158,85 @@ class QuestionAnswerPairSnapshot(BaseModel):
}
def fetch_and_process_chat_session_history_minimal(
db_session: Session,
start: datetime,
end: datetime,
feedback_filter: QAFeedbackType | None = None,
limit: int | None = 500,
) -> list[ChatSessionMinimal]:
chat_sessions = fetch_chat_sessions_eagerly_by_time(
start=start, end=end, db_session=db_session, limit=limit
)
minimal_sessions = []
for chat_session in chat_sessions:
if not chat_session.messages:
continue
first_user_message = next(
(
message.message
for message in chat_session.messages
if message.message_type == MessageType.USER
),
"",
)
first_ai_message = next(
(
message.message
for message in chat_session.messages
if message.message_type == MessageType.ASSISTANT
),
"",
)
has_positive_feedback = any(
feedback.is_positive
for message in chat_session.messages
for feedback in message.chat_message_feedbacks
)
has_negative_feedback = any(
not feedback.is_positive
for message in chat_session.messages
for feedback in message.chat_message_feedbacks
)
feedback_type: QAFeedbackType | Literal["mixed"] | None = (
"mixed"
if has_positive_feedback and has_negative_feedback
else QAFeedbackType.LIKE
if has_positive_feedback
else QAFeedbackType.DISLIKE
if has_negative_feedback
else None
)
if feedback_filter:
if feedback_filter == QAFeedbackType.LIKE and not has_positive_feedback:
continue
if feedback_filter == QAFeedbackType.DISLIKE and not has_negative_feedback:
continue
minimal_sessions.append(
ChatSessionMinimal(
id=chat_session.id,
user_email=get_display_email(
chat_session.user.email if chat_session.user else None
),
name=chat_session.description,
first_user_message=first_user_message,
first_ai_message=first_ai_message,
persona_name=chat_session.persona.name,
time_created=chat_session.time_created,
feedback_type=feedback_type,
)
)
return minimal_sessions
def fetch_and_process_chat_session_history(
db_session: Session,
start: datetime,
@ -153,7 +244,7 @@ def fetch_and_process_chat_session_history(
feedback_type: QAFeedbackType | None,
limit: int | None = 500,
) -> list[ChatSessionSnapshot]:
chat_sessions = fetch_chat_sessions_by_time(
chat_sessions = fetch_chat_sessions_eagerly_by_time(
start=start, end=end, db_session=db_session, limit=limit
)
@ -214,15 +305,15 @@ def get_chat_session_history(
end: datetime | None = None,
_: db_models.User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[ChatSessionSnapshot]:
return fetch_and_process_chat_session_history(
) -> list[ChatSessionMinimal]:
return fetch_and_process_chat_session_history_minimal(
db_session=db_session,
start=start
or (
datetime.now(tz=timezone.utc) - timedelta(days=30)
), # default is 30d lookback
end=end or datetime.now(tz=timezone.utc),
feedback_type=feedback_type,
feedback_filter=feedback_type,
)

View File

@ -1,7 +1,7 @@
import { errorHandlingFetcher } from "@/lib/fetcher";
import useSWR, { mutate } from "swr";
import {
ChatSessionSnapshot,
ChatSessionMinimal,
DanswerBotAnalytics,
QueryAnalytics,
UserAnalytics,
@ -74,7 +74,7 @@ export const useQueryHistory = () => {
start: convertDateToStartOfDay(timeRange.from)?.toISOString(),
end: convertDateToEndOfDay(timeRange.to)?.toISOString(),
});
const swrResponse = useSWR<ChatSessionSnapshot[]>(url, errorHandlingFetcher);
const swrResponse = useSWR<ChatSessionMinimal[]>(url, errorHandlingFetcher);
return {
...swrResponse,

View File

@ -13,7 +13,7 @@ import {
import { Divider } from "@tremor/react";
import { Select, SelectItem } from "@tremor/react";
import { ThreeDotsLoader } from "@/components/Loading";
import { ChatSessionSnapshot } from "../usage/types";
import { ChatSessionMinimal } from "../usage/types";
import { timestampToReadableDate } from "@/lib/dateUtils";
import { FiFrown, FiMinus, FiSmile } from "react-icons/fi";
import { useState } from "react";
@ -27,48 +27,37 @@ import { DownloadAsCSV } from "./DownloadAsCSV";
const NUM_IN_PAGE = 20;
function QueryHistoryTableRow({
chatSessionSnapshot,
chatSessionMinimal,
}: {
chatSessionSnapshot: ChatSessionSnapshot;
chatSessionMinimal: ChatSessionMinimal;
}) {
let finalFeedback: Feedback | "mixed" | null = null;
for (const message of chatSessionSnapshot.messages) {
if (message.feedback_type) {
if (finalFeedback === null) {
finalFeedback = message.feedback_type;
} else if (finalFeedback !== message.feedback_type) {
finalFeedback = "mixed";
}
}
}
return (
<TableRow
key={chatSessionSnapshot.id}
key={chatSessionMinimal.id}
className="hover:bg-hover-light cursor-pointer relative"
>
<TableCell>
<Text className="whitespace-normal line-clamp-5">
{chatSessionSnapshot.messages[0]?.message || "-"}
{chatSessionMinimal.first_user_message || "-"}
</Text>
</TableCell>
<TableCell>
<Text className="whitespace-normal line-clamp-5">
{chatSessionSnapshot.messages[1]?.message || "-"}
{chatSessionMinimal.first_ai_message || "-"}
</Text>
</TableCell>
<TableCell>
<FeedbackBadge feedback={finalFeedback} />
<FeedbackBadge feedback={chatSessionMinimal.feedback_type} />
</TableCell>
<TableCell>{chatSessionSnapshot.user_email || "-"}</TableCell>
<TableCell>{chatSessionSnapshot.persona_name || "Unknown"}</TableCell>
<TableCell>{chatSessionMinimal.user_email || "-"}</TableCell>
<TableCell>{chatSessionMinimal.persona_name || "Unknown"}</TableCell>
<TableCell>
{timestampToReadableDate(chatSessionSnapshot.time_created)}
{timestampToReadableDate(chatSessionMinimal.time_created)}
</TableCell>
{/* Wrapping in <td> to avoid console warnings */}
<td className="w-0 p-0">
<Link
href={`/admin/performance/query-history/${chatSessionSnapshot.id}`}
href={`/admin/performance/query-history/${chatSessionMinimal.id}`}
className="absolute w-full h-full left-0"
></Link>
</td>
@ -152,10 +141,10 @@ export function QueryHistoryTable() {
<TableBody>
{chatSessionData
.slice(NUM_IN_PAGE * (page - 1), NUM_IN_PAGE * page)
.map((chatSessionSnapshot) => (
.map((chatSessionMinimal) => (
<QueryHistoryTableRow
key={chatSessionSnapshot.id}
chatSessionSnapshot={chatSessionSnapshot}
key={chatSessionMinimal.id}
chatSessionMinimal={chatSessionMinimal}
/>
))}
</TableBody>

View File

@ -41,3 +41,14 @@ export interface ChatSessionSnapshot {
persona_name: string | null;
time_created: string;
}
export interface ChatSessionMinimal {
id: number;
user_email: string | null;
name: string | null;
first_user_message: string;
first_ai_message: string;
persona_name: string | null;
time_created: string;
feedback_type: Feedback | "mixed" | null;
}