Skip to content

Commit

Permalink
feat: advanced filters for feedbacks and chats admin api (#525)
Browse files Browse the repository at this point in the history
part of #389

---------

Co-authored-by: Mini256 <[email protected]>
  • Loading branch information
sszgwdk and Mini256 authored Feb 11, 2025
1 parent ce7f50e commit 13604e6
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 33 deletions.
Empty file.
23 changes: 23 additions & 0 deletions backend/app/api/admin_routes/chat/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import Optional
from fastapi import APIRouter, Depends
from fastapi_pagination import Page, Params

from app.models.chat import ChatOrigin
from app.api.deps import CurrentSuperuserDep, SessionDep
from app.repositories import chat_repo


router = APIRouter(
prefix="/admin/chats",
tags=["admin/chats"],
)


@router.get("/origins")
def list_chat_origins(
db_session: SessionDep,
user: CurrentSuperuserDep,
search: Optional[str] = None,
params: Params = Depends(),
) -> Page[ChatOrigin]:
return chat_repo.list_chat_origins(db_session, search, params)
46 changes: 26 additions & 20 deletions backend/app/api/admin_routes/feedback.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
from fastapi import APIRouter, Depends
from typing import Annotated, Optional

from fastapi import APIRouter, Depends, Query
from fastapi_pagination import Params, Page
from fastapi_pagination.ext.sqlmodel import paginate
from sqlmodel import select

from app.api.deps import SessionDep, CurrentSuperuserDep
from app.models import Feedback, AdminFeedbackPublic
from app.models import AdminFeedbackPublic, FeedbackFilters
from app.models.feedback import FeedbackOrigin
from app.repositories import feedback_repo

router = APIRouter()
router = APIRouter(
prefix="/admin/feedbacks",
tags=["admin/feedback"],
)


@router.get("/admin/feedbacks")
@router.get("/")
def list_feedbacks(
session: SessionDep,
user: CurrentSuperuserDep,
filters: Annotated[FeedbackFilters, Query()],
params: Params = Depends(),
) -> Page[AdminFeedbackPublic]:
return paginate(
session,
select(Feedback).order_by(Feedback.created_at.desc()),
params,
transformer=lambda items: [
AdminFeedbackPublic(
**item.model_dump(),
chat_title=item.chat.title,
chat_origin=item.chat.origin,
chat_message_content=item.chat_message.content,
user_email=item.user.email if item.user else None,
)
for item in items
],
return feedback_repo.paginate(
session=session,
filters=filters,
params=params,
)


@router.get("/origins")
def list_feedback_origins(
session: SessionDep,
user: CurrentSuperuserDep,
search: Optional[str] = None,
params: Params = Depends(),
) -> Page[FeedbackOrigin]:
return feedback_repo.list_feedback_origins(session, search, params)
1 change: 1 addition & 0 deletions backend/app/api/admin_routes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class EmbeddingModelDescriptor(EmbeddingModelItem):

class UserDescriptor(BaseModel):
id: UUID
email: str


class KnowledgeBaseDescriptor(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/admin_routes/stats.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from datetime import date
from pydantic import BaseModel
from fastapi import APIRouter

from app.api.deps import CurrentSuperuserDep, SessionDep
from app.repositories import chat_repo


router = APIRouter()


Expand Down
24 changes: 24 additions & 0 deletions backend/app/api/admin_routes/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Optional
from fastapi import APIRouter, Depends
from fastapi_pagination import Page, Params

from app.repositories.user import user_repo
from app.api.deps import SessionDep, CurrentSuperuserDep
from app.api.admin_routes.models import (
UserDescriptor,
)

router = APIRouter(
prefix="/admin/users",
tags=["admin/users"],
)


@router.get("/search")
def search_users(
db_session: SessionDep,
user: CurrentSuperuserDep,
search: Optional[str] = None,
params: Params = Depends(),
) -> Page[UserDescriptor]:
return user_repo.search_users(db_session, search, params)
10 changes: 6 additions & 4 deletions backend/app/api/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from fastapi import APIRouter


from app.api.routes import (
index,
chat,
Expand Down Expand Up @@ -35,6 +33,7 @@
from app.api.admin_routes.reranker_model.routes import (
router as admin_reranker_model_router,
)
from app.api.admin_routes.chat.routes import router as admin_user_router
from app.api.admin_routes import (
chat_engine as admin_chat_engine,
feedback as admin_feedback,
Expand All @@ -44,6 +43,7 @@
stats as admin_stats,
semantic_cache as admin_semantic_cache,
langfuse as admin_langfuse,
user as admin_user,
)
from app.api.admin_routes.evaluation import (
evaluation_task as admin_evaluation_task,
Expand All @@ -63,9 +63,10 @@
api_router.include_router(api_key.router, tags=["auth"])
api_router.include_router(document.router, tags=["documents"])
api_router.include_router(retrieve_routes.router, tags=["retrieve"])
api_router.include_router(admin_chat_engine.router, tags=["admin/chat_engine"])
api_router.include_router(admin_user_router)
api_router.include_router(admin_chat_engine.router, tags=["admin/chat-engines"])
api_router.include_router(admin_document_router, tags=["admin/documents"])
api_router.include_router(admin_feedback.router, tags=["admin/feedback"])
api_router.include_router(admin_feedback.router)
api_router.include_router(admin_site_settings.router, tags=["admin/site_settings"])
api_router.include_router(admin_upload.router, tags=["admin/upload"])
api_router.include_router(admin_knowledge_base_router, tags=["admin/knowledge_base"])
Expand All @@ -92,6 +93,7 @@
api_router.include_router(
admin_evaluation_dataset.router, tags=["admin/evaluation/dataset"]
)
api_router.include_router(admin_user.router)

api_router.include_router(
fastapi_users.get_auth_router(auth_backend), prefix="/auth", tags=["auth"]
Expand Down
11 changes: 6 additions & 5 deletions backend/app/api/routes/chat.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import logging
from uuid import UUID
from typing import List, Optional
from typing import List, Optional, Annotated
from http import HTTPStatus

from pydantic import (
BaseModel,
field_validator,
)
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Request, Query
from fastapi.responses import StreamingResponse
from fastapi_pagination import Params, Page

from app.api.deps import SessionDep, OptionalUserDep, CurrentUserDep
from app.rag.chat.chat_flow import ChatFlow
from app.rag.retrievers.knowledge_graph.schema import KnowledgeGraphRetrievalResult
from app.repositories import chat_repo
from app.models import Chat, ChatUpdate

from app.rag.chat.chat_service import get_final_chat_result
from app.models import Chat, ChatUpdate, ChatFilters
from app.rag.chat.chat_service import (
get_final_chat_result,
user_can_view_chat,
user_can_edit_chat,
get_chat_message_subgraph,
Expand Down Expand Up @@ -99,10 +99,11 @@ def list_chats(
request: Request,
session: SessionDep,
user: OptionalUserDep,
filters: Annotated[ChatFilters, Query()],
params: Params = Depends(),
) -> Page[Chat]:
browser_id = request.state.browser_id
return chat_repo.paginate(session, user, browser_id, params)
return chat_repo.paginate(session, user, browser_id, filters, params)


@router.get("/chats/{chat_id}")
Expand Down
4 changes: 3 additions & 1 deletion backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
Feedback,
FeedbackType,
AdminFeedbackPublic,
FeedbackFilters,
FeedbackOrigin,
)
from .semantic_cache import SemanticCache
from .staff_action_log import StaffActionLog
from .chat_engine import ChatEngine, ChatEngineUpdate
from .chat import Chat, ChatUpdate, ChatVisibility
from .chat import Chat, ChatUpdate, ChatVisibility, ChatFilters, ChatOrigin
from .chat_message import ChatMessage
from .document import Document, DocIndexTaskStatus
from .chunk import Chunk, KgIndexStatus
Expand Down
15 changes: 15 additions & 0 deletions backend/app/models/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,18 @@ class Chat(UUIDBaseModel, UpdatableBaseModel, table=True):
class ChatUpdate(BaseModel):
title: Optional[str] = None
visibility: Optional[ChatVisibility] = None


class ChatFilters(BaseModel):
created_at_start: Optional[datetime] = None
created_at_end: Optional[datetime] = None
updated_at_start: Optional[datetime] = None
updated_at_end: Optional[datetime] = None
chat_origin: Optional[str] = None
# user_id: Optional[UUID] = None # no use now
engine_id: Optional[int] = None


class ChatOrigin(BaseModel):
origin: str
chats: int
16 changes: 16 additions & 0 deletions backend/app/models/feedback.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import enum
from uuid import UUID
from typing import Optional
from pydantic import BaseModel
from datetime import datetime

from sqlmodel import (
Field,
Expand Down Expand Up @@ -63,3 +65,17 @@ class AdminFeedbackPublic(BaseFeedback):
chat_message_content: str
user_id: Optional[UUID]
user_email: Optional[str]


class FeedbackFilters(BaseModel):
created_at_start: Optional[datetime] = None
created_at_end: Optional[datetime] = None
feedback_origin: Optional[str] = None
chat_id: Optional[UUID] = None
feedback_type: Optional[FeedbackType] = None
user_id: Optional[UUID] = None


class FeedbackOrigin(BaseModel):
origin: str
feedbacks: int
1 change: 1 addition & 0 deletions backend/app/repositories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
from .chunk import chunk_repo
from .data_source import data_source_repo
from .knowledge_base import knowledge_base_repo
from .feedback import feedback_repo
from .llm import llm_repo
from .embedding_model import embed_model_repo
49 changes: 47 additions & 2 deletions backend/app/repositories/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from datetime import datetime, UTC, date, timedelta
from collections import defaultdict

from sqlmodel import select, Session, or_, func, case, desc
from sqlmodel import select, Session, or_, func, case, desc, col
from fastapi_pagination import Params, Page
from fastapi_pagination.ext.sqlmodel import paginate

from app.models import Chat, User, ChatMessage, ChatUpdate
from app.models import Chat, User, ChatMessage, ChatUpdate, ChatFilters, ChatOrigin
from app.repositories.base_repo import BaseRepo
from app.exceptions import ChatNotFound, ChatMessageNotFound

Expand All @@ -21,6 +21,7 @@ def paginate(
session: Session,
user: User | None,
browser_id: str | None,
filters: ChatFilters,
params: Params | None = Params(),
) -> Page[Chat]:
query = select(Chat).where(Chat.deleted_at == None)
Expand All @@ -31,6 +32,23 @@ def paginate(
)
else:
query = query.where(Chat.browser_id == browser_id, Chat.user_id == None)

# filters
if filters.created_at_start:
query = query.where(Chat.created_at >= filters.created_at_start)
if filters.created_at_end:
query = query.where(Chat.created_at <= filters.created_at_end)
if filters.updated_at_start:
query = query.where(Chat.updated_at >= filters.updated_at_start)
if filters.updated_at_end:
query = query.where(Chat.updated_at <= filters.updated_at_end)
if filters.chat_origin:
query = query.where(col(Chat.origin).contains(filters.chat_origin))
# if filters.user_id:
# query = query.where(Chat.user_id == filters.user_id)
if filters.engine_id:
query = query.where(Chat.engine_id == filters.engine_id)

query = query.order_by(Chat.created_at.desc())
return paginate(session, query, params)

Expand Down Expand Up @@ -224,5 +242,32 @@ def chat_trend_by_origin(
stats.sort(key=lambda x: x["date"])
return stats

def list_chat_origins(
self,
db_session: Session,
search: Optional[str] = None,
params: Params = Params(),
) -> Page[ChatOrigin]:
query = (
select(Chat.origin, func.count(Chat.id).label("chats"))
.where(Chat.deleted_at == None)
.where(Chat.origin != None)
.where(Chat.origin != "")
)

if search:
query = query.where(Chat.origin.ilike(f"%{search}%"))

query = query.group_by(Chat.origin).order_by(desc("chats"))

return paginate(
db_session,
query,
params,
transformer=lambda chats: [
ChatOrigin(origin=chat.origin, chats=chat.chats) for chat in chats
],
)


chat_repo = ChatRepo()
Loading

0 comments on commit 13604e6

Please sign in to comment.