mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-17 11:30:58 +02:00
Query history speed fix (#109)
This commit is contained in:
parent
733d4e666b
commit
98a58337a7
@ -3,30 +3,48 @@ from typing import Literal
|
|||||||
|
|
||||||
from sqlalchemy import asc
|
from sqlalchemy import asc
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
from sqlalchemy.orm import contains_eager
|
||||||
|
from sqlalchemy.orm import joinedload
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from danswer.db.models import ChatMessage
|
||||||
from danswer.db.models import ChatSession
|
from danswer.db.models import ChatSession
|
||||||
|
|
||||||
SortByOptions = Literal["time_sent"]
|
SortByOptions = Literal["time_sent"]
|
||||||
|
|
||||||
|
|
||||||
def fetch_chat_sessions_by_time(
|
def fetch_chat_sessions_eagerly_by_time(
|
||||||
start: datetime.datetime,
|
start: datetime.datetime,
|
||||||
end: datetime.datetime,
|
end: datetime.datetime,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
ascending: bool = False,
|
ascending: bool = False,
|
||||||
limit: int | None = 500,
|
limit: int | None = 500,
|
||||||
) -> list[ChatSession]:
|
) -> 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 = (
|
query = (
|
||||||
db_session.query(ChatSession)
|
db_session.query(ChatSession)
|
||||||
.filter(ChatSession.time_created >= start, ChatSession.time_created <= end)
|
.join(subquery, ChatSession.id == subquery.c.id)
|
||||||
.order_by(order)
|
.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()
|
chat_sessions = query.all()
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import io
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from fastapi import Depends
|
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.engine import get_session
|
||||||
from danswer.db.models import ChatMessage
|
from danswer.db.models import ChatMessage
|
||||||
from danswer.db.models import ChatSession
|
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()
|
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):
|
class ChatSessionSnapshot(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
user_email: str
|
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(
|
def fetch_and_process_chat_session_history(
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
start: datetime,
|
start: datetime,
|
||||||
@ -153,7 +244,7 @@ def fetch_and_process_chat_session_history(
|
|||||||
feedback_type: QAFeedbackType | None,
|
feedback_type: QAFeedbackType | None,
|
||||||
limit: int | None = 500,
|
limit: int | None = 500,
|
||||||
) -> list[ChatSessionSnapshot]:
|
) -> 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
|
start=start, end=end, db_session=db_session, limit=limit
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -214,15 +305,15 @@ def get_chat_session_history(
|
|||||||
end: datetime | None = None,
|
end: datetime | None = None,
|
||||||
_: db_models.User | None = Depends(current_admin_user),
|
_: db_models.User | None = Depends(current_admin_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
) -> list[ChatSessionSnapshot]:
|
) -> list[ChatSessionMinimal]:
|
||||||
return fetch_and_process_chat_session_history(
|
return fetch_and_process_chat_session_history_minimal(
|
||||||
db_session=db_session,
|
db_session=db_session,
|
||||||
start=start
|
start=start
|
||||||
or (
|
or (
|
||||||
datetime.now(tz=timezone.utc) - timedelta(days=30)
|
datetime.now(tz=timezone.utc) - timedelta(days=30)
|
||||||
), # default is 30d lookback
|
), # default is 30d lookback
|
||||||
end=end or datetime.now(tz=timezone.utc),
|
end=end or datetime.now(tz=timezone.utc),
|
||||||
feedback_type=feedback_type,
|
feedback_filter=feedback_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
import {
|
import {
|
||||||
ChatSessionSnapshot,
|
ChatSessionMinimal,
|
||||||
DanswerBotAnalytics,
|
DanswerBotAnalytics,
|
||||||
QueryAnalytics,
|
QueryAnalytics,
|
||||||
UserAnalytics,
|
UserAnalytics,
|
||||||
@ -74,7 +74,7 @@ export const useQueryHistory = () => {
|
|||||||
start: convertDateToStartOfDay(timeRange.from)?.toISOString(),
|
start: convertDateToStartOfDay(timeRange.from)?.toISOString(),
|
||||||
end: convertDateToEndOfDay(timeRange.to)?.toISOString(),
|
end: convertDateToEndOfDay(timeRange.to)?.toISOString(),
|
||||||
});
|
});
|
||||||
const swrResponse = useSWR<ChatSessionSnapshot[]>(url, errorHandlingFetcher);
|
const swrResponse = useSWR<ChatSessionMinimal[]>(url, errorHandlingFetcher);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...swrResponse,
|
...swrResponse,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import { Divider } from "@tremor/react";
|
import { Divider } from "@tremor/react";
|
||||||
import { Select, SelectItem } from "@tremor/react";
|
import { Select, SelectItem } from "@tremor/react";
|
||||||
import { ThreeDotsLoader } from "@/components/Loading";
|
import { ThreeDotsLoader } from "@/components/Loading";
|
||||||
import { ChatSessionSnapshot } from "../usage/types";
|
import { ChatSessionMinimal } from "../usage/types";
|
||||||
import { timestampToReadableDate } from "@/lib/dateUtils";
|
import { timestampToReadableDate } from "@/lib/dateUtils";
|
||||||
import { FiFrown, FiMinus, FiSmile } from "react-icons/fi";
|
import { FiFrown, FiMinus, FiSmile } from "react-icons/fi";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -27,48 +27,37 @@ import { DownloadAsCSV } from "./DownloadAsCSV";
|
|||||||
const NUM_IN_PAGE = 20;
|
const NUM_IN_PAGE = 20;
|
||||||
|
|
||||||
function QueryHistoryTableRow({
|
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 (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={chatSessionSnapshot.id}
|
key={chatSessionMinimal.id}
|
||||||
className="hover:bg-hover-light cursor-pointer relative"
|
className="hover:bg-hover-light cursor-pointer relative"
|
||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Text className="whitespace-normal line-clamp-5">
|
<Text className="whitespace-normal line-clamp-5">
|
||||||
{chatSessionSnapshot.messages[0]?.message || "-"}
|
{chatSessionMinimal.first_user_message || "-"}
|
||||||
</Text>
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Text className="whitespace-normal line-clamp-5">
|
<Text className="whitespace-normal line-clamp-5">
|
||||||
{chatSessionSnapshot.messages[1]?.message || "-"}
|
{chatSessionMinimal.first_ai_message || "-"}
|
||||||
</Text>
|
</Text>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<FeedbackBadge feedback={finalFeedback} />
|
<FeedbackBadge feedback={chatSessionMinimal.feedback_type} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{chatSessionSnapshot.user_email || "-"}</TableCell>
|
<TableCell>{chatSessionMinimal.user_email || "-"}</TableCell>
|
||||||
<TableCell>{chatSessionSnapshot.persona_name || "Unknown"}</TableCell>
|
<TableCell>{chatSessionMinimal.persona_name || "Unknown"}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{timestampToReadableDate(chatSessionSnapshot.time_created)}
|
{timestampToReadableDate(chatSessionMinimal.time_created)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{/* Wrapping in <td> to avoid console warnings */}
|
{/* Wrapping in <td> to avoid console warnings */}
|
||||||
<td className="w-0 p-0">
|
<td className="w-0 p-0">
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/performance/query-history/${chatSessionSnapshot.id}`}
|
href={`/admin/performance/query-history/${chatSessionMinimal.id}`}
|
||||||
className="absolute w-full h-full left-0"
|
className="absolute w-full h-full left-0"
|
||||||
></Link>
|
></Link>
|
||||||
</td>
|
</td>
|
||||||
@ -152,10 +141,10 @@ export function QueryHistoryTable() {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{chatSessionData
|
{chatSessionData
|
||||||
.slice(NUM_IN_PAGE * (page - 1), NUM_IN_PAGE * page)
|
.slice(NUM_IN_PAGE * (page - 1), NUM_IN_PAGE * page)
|
||||||
.map((chatSessionSnapshot) => (
|
.map((chatSessionMinimal) => (
|
||||||
<QueryHistoryTableRow
|
<QueryHistoryTableRow
|
||||||
key={chatSessionSnapshot.id}
|
key={chatSessionMinimal.id}
|
||||||
chatSessionSnapshot={chatSessionSnapshot}
|
chatSessionMinimal={chatSessionMinimal}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
@ -41,3 +41,14 @@ export interface ChatSessionSnapshot {
|
|||||||
persona_name: string | null;
|
persona_name: string | null;
|
||||||
time_created: string;
|
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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user