Skip to content

Commit cda1f90

Browse files
authored
add user question cache (#640)
1 parent 07eb3c8 commit cda1f90

File tree

2 files changed

+127
-49
lines changed

2 files changed

+127
-49
lines changed

backend/app/rag/chat/chat_flow.py

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ def __init__(
105105
user_id=self.user.id if self.user else None,
106106
browser_id=self.browser_id,
107107
origin=origin,
108-
visibility=ChatVisibility.PUBLIC
109-
if not self.user
110-
else ChatVisibility.PRIVATE,
108+
visibility=(
109+
ChatVisibility.PUBLIC
110+
if not self.user
111+
else ChatVisibility.PRIVATE
112+
),
111113
),
112114
)
113115
chat_id = self.db_chat_obj.id
@@ -162,9 +164,9 @@ def chat(self) -> Generator[ChatEvent | str, None, None]:
162164
try:
163165
with self._trace_manager.observe(
164166
trace_name="ChatFlow",
165-
user_id=self.user.email
166-
if self.user
167-
else f"anonymous-{self.browser_id}",
167+
user_id=(
168+
self.user.email if self.user else f"anonymous-{self.browser_id}"
169+
),
168170
metadata={
169171
"is_external_engine": self.engine_config.is_external_engine,
170172
"chat_engine_config": self.engine_config.screenshot(),
@@ -259,7 +261,7 @@ def _chat_start(
259261
chat_message=DBChatMessage(
260262
role=MessageRole.USER.value,
261263
trace_url=self._trace_manager.trace_url,
262-
content=self.user_question,
264+
content=self.user_question.strip(),
263265
),
264266
)
265267
db_assistant_message = chat_repo.create_message(
@@ -543,11 +545,13 @@ def _post_verification(
543545
"external_request_id": external_request_id,
544546
"qa_content": qa_content,
545547
},
546-
headers={
547-
"Authorization": f"Bearer {post_verification_token}",
548-
}
549-
if post_verification_token
550-
else {},
548+
headers=(
549+
{
550+
"Authorization": f"Bearer {post_verification_token}",
551+
}
552+
if post_verification_token
553+
else {}
554+
),
551555
timeout=10,
552556
)
553557
resp.raise_for_status()
@@ -619,48 +623,65 @@ def _chat_finish(
619623
def _external_chat(self) -> Generator[ChatEvent | str, None, None]:
620624
db_user_message, db_assistant_message = yield from self._chat_start()
621625

626+
cache_messages = None
622627
goal, response_format = self.user_question, {}
623-
try:
624-
# 1. Generate the goal with the user question, knowledge graph and chat history.
625-
goal, response_format = yield from self._generate_goal()
626-
627-
# 2. Check if the goal provided enough context information or need to clarify.
628-
if self.engine_config.clarify_question:
629-
need_clarify, need_clarify_response = yield from self._clarify_question(
630-
user_question=goal, chat_history=self.chat_history
628+
if settings.ENABLE_QUESTION_CACHE and len(self.chat_history) == 0:
629+
try:
630+
logger.info(f"start to find_best_answer_for_question with question: {self.user_question}")
631+
cache_messages = chat_repo.find_best_answer_for_question(
632+
self.db_session, self.user_question
631633
)
632-
if need_clarify:
633-
yield from self._chat_finish(
634-
db_assistant_message=db_assistant_message,
635-
db_user_message=db_user_message,
636-
response_text=need_clarify_response,
637-
annotation_silent=True,
638-
)
639-
return
640-
except Exception as e:
641-
goal = self.user_question
642-
logger.warning(
643-
f"Failed to generate refined goal, fallback to use user question as goal directly: {e}",
644-
exc_info=True,
645-
extra={},
646-
)
634+
if cache_messages and len(cache_messages) > 0:
635+
logger.info(f"find_best_answer_for_question result {len(cache_messages)} for question {self.user_question}")
636+
except Exception as e:
637+
logger.error(f"Failed to find best answer for question {self.user_question}: {e}")
647638

648-
cache_messages = None
649-
if settings.ENABLE_QUESTION_CACHE:
639+
if not cache_messages or len(cache_messages) == 0:
650640
try:
651-
logger.info(
652-
f"start to find_recent_assistant_messages_by_goal with goal: {goal}, response_format: {response_format}"
653-
)
654-
cache_messages = chat_repo.find_recent_assistant_messages_by_goal(
655-
self.db_session,
656-
{"goal": goal, "Lang": response_format.get("Lang", "English")},
657-
90,
658-
)
659-
logger.info(
660-
f"find_recent_assistant_messages_by_goal result {len(cache_messages)} for goal {goal}"
661-
)
641+
# 1. Generate the goal with the user question, knowledge graph and chat history.
642+
goal, response_format = yield from self._generate_goal()
643+
644+
# 2. Check if the goal provided enough context information or need to clarify.
645+
if self.engine_config.clarify_question:
646+
need_clarify, need_clarify_response = (
647+
yield from self._clarify_question(
648+
user_question=goal, chat_history=self.chat_history
649+
)
650+
)
651+
if need_clarify:
652+
yield from self._chat_finish(
653+
db_assistant_message=db_assistant_message,
654+
db_user_message=db_user_message,
655+
response_text=need_clarify_response,
656+
annotation_silent=True,
657+
)
658+
return
662659
except Exception as e:
663-
logger.error(f"Failed to find recent assistant messages by goal: {e}")
660+
goal = self.user_question
661+
logger.warning(
662+
f"Failed to generate refined goal, fallback to use user question as goal directly: {e}",
663+
exc_info=True,
664+
extra={},
665+
)
666+
667+
cache_messages = None
668+
if settings.ENABLE_QUESTION_CACHE:
669+
try:
670+
logger.info(
671+
f"start to find_recent_assistant_messages_by_goal with goal: {goal}, response_format: {response_format}"
672+
)
673+
cache_messages = chat_repo.find_recent_assistant_messages_by_goal(
674+
self.db_session,
675+
{"goal": goal, "Lang": response_format.get("Lang", "English")},
676+
90,
677+
)
678+
logger.info(
679+
f"find_recent_assistant_messages_by_goal result {len(cache_messages)} for goal {goal}"
680+
)
681+
except Exception as e:
682+
logger.error(
683+
f"Failed to find recent assistant messages by goal: {e}"
684+
)
664685

665686
stream_chat_api_url = (
666687
self.engine_config.external_engine_config.stream_chat_api_url

backend/app/repositories/chat.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,63 @@ def find_recent_assistant_messages_by_goal(
187187

188188
return session.exec(query).all()
189189

190+
def find_best_answer_for_question(
191+
self, session: Session, user_question: str
192+
) -> List[ChatMessage]:
193+
"""Find best answer messages for a specific user question.
194+
195+
This method finds assistant messages that:
196+
1. Are marked as best answers
197+
2. Are responses (ordinal=2) to the exact user question
198+
3. Were created within the last 15 days
199+
200+
Args:
201+
session: Database session
202+
user_question: The exact question text to search for
203+
204+
Returns:
205+
List of matching assistant messages marked as best answers
206+
"""
207+
cutoff = datetime.now(UTC) - timedelta(days=15)
208+
209+
# First, get all best answers from assistant (using the is_best_answer index)
210+
best_answer_chat_ids = (
211+
select(ChatMessage.chat_id)
212+
.where(
213+
ChatMessage.is_best_answer == 1, # Using the index for efficiency
214+
ChatMessage.role == "assistant",
215+
ChatMessage.ordinal == 2,
216+
ChatMessage.created_at >= cutoff
217+
)
218+
)
219+
220+
# Then, find user questions that match our target question and belong to chats with best answers
221+
matching_chat_ids = (
222+
select(ChatMessage.chat_id)
223+
.where(
224+
ChatMessage.chat_id.in_(best_answer_chat_ids),
225+
ChatMessage.role == "user",
226+
ChatMessage.ordinal == 1,
227+
ChatMessage.content == user_question.strip()
228+
)
229+
)
230+
231+
# Finally, get the best answers that correspond to the matching user questions
232+
query = (
233+
select(ChatMessage)
234+
.where(
235+
ChatMessage.is_best_answer == 1,
236+
ChatMessage.role == "assistant",
237+
ChatMessage.ordinal == 2,
238+
ChatMessage.chat_id.in_(matching_chat_ids)
239+
)
240+
)
241+
242+
query = query.order_by(desc(ChatMessage.created_at))
243+
244+
# Execute the query and return all results
245+
return session.exec(query).all()
246+
190247
def chat_trend_by_user(
191248
self, session: Session, start_date: date, end_date: date
192249
) -> List[dict]:

0 commit comments

Comments
 (0)