2025-07-24 14:43:48 -07:00
from typing import Any
2025-06-03 00:10:35 -07:00
from langchain_core . messages import HumanMessage , SystemMessage
2025-07-24 14:43:48 -07:00
from langchain_core . runnables import RunnableConfig
from app . services . reranker_service import RerankerService
2025-06-05 20:33:09 -07:00
from . . utils import (
calculate_token_count ,
2025-07-25 15:11:19 -07:00
format_documents_section ,
2025-08-27 18:43:33 -07:00
langchain_chat_history_to_str ,
2025-07-24 14:43:48 -07:00
optimize_documents_for_token_limit ,
2025-07-25 15:11:19 -07:00
)
2025-07-24 14:43:48 -07:00
from . configuration import Configuration
from . prompts import get_qna_citation_system_prompt , get_qna_no_documents_system_prompt
from . state import State
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
2025-07-24 14:43:48 -07:00
async def rerank_documents ( state : State , config : RunnableConfig ) - > dict [ str , Any ] :
2025-06-03 00:10:35 -07:00
"""
Rerank the documents based on relevance to the user ' s question.
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
This node takes the relevant documents provided in the configuration ,
reranks them using the reranker service based on the user ' s query,
and updates the state with the reranked documents .
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
Returns :
Dict containing the reranked documents .
"""
# Get configuration and relevant documents
configuration = Configuration . from_runnable_config ( config )
documents = configuration . relevant_documents
user_query = configuration . user_query
2025-06-05 23:52:34 -07:00
reformulated_query = configuration . reformulated_query
2025-06-03 00:10:35 -07:00
# If no documents were provided, return empty list
if not documents or len ( documents ) == 0 :
2025-07-25 15:11:19 -07:00
return { " reranked_documents " : [ ] }
2025-06-03 00:10:35 -07:00
# Get reranker service from app config
2025-07-03 14:09:36 -07:00
reranker_service = RerankerService . get_reranker_instance ( )
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
# Use documents as is if no reranker service is available
reranked_docs = documents
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
if reranker_service :
try :
# Convert documents to format expected by reranker if needed
reranker_input_docs = [
{
" chunk_id " : doc . get ( " chunk_id " , f " chunk_ { i } " ) ,
" content " : doc . get ( " content " , " " ) ,
" score " : doc . get ( " score " , 0.0 ) ,
" document " : {
" id " : doc . get ( " document " , { } ) . get ( " id " , " " ) ,
" title " : doc . get ( " document " , { } ) . get ( " title " , " " ) ,
2025-07-25 15:11:19 -07:00
" document_type " : doc . get ( " document " , { } ) . get (
" document_type " , " "
) ,
" metadata " : doc . get ( " document " , { } ) . get ( " metadata " , { } ) ,
} ,
}
for i , doc in enumerate ( documents )
2025-06-03 00:10:35 -07:00
]
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
# Rerank documents using the user's query
2025-07-25 15:11:19 -07:00
reranked_docs = reranker_service . rerank_documents (
user_query + " \n " + reformulated_query , reranker_input_docs
)
2025-06-03 00:10:35 -07:00
# Sort by score in descending order
reranked_docs . sort ( key = lambda x : x . get ( " score " , 0 ) , reverse = True )
2025-07-25 15:11:19 -07:00
print (
f " Reranked { len ( reranked_docs ) } documents for Q&A query: { user_query } "
)
2025-06-03 00:10:35 -07:00
except Exception as e :
2025-07-24 14:43:48 -07:00
print ( f " Error during reranking: { e !s} " )
2025-06-03 00:10:35 -07:00
# Use original docs if reranking fails
2025-07-25 15:11:19 -07:00
return { " reranked_documents " : reranked_docs }
2025-06-03 00:10:35 -07:00
2025-07-24 14:43:48 -07:00
async def answer_question ( state : State , config : RunnableConfig ) - > dict [ str , Any ] :
2025-06-03 00:10:35 -07:00
"""
Answer the user ' s question using the provided documents.
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
This node takes the relevant documents provided in the configuration and uses
an LLM to generate a comprehensive answer to the user ' s question with
2025-07-25 15:11:19 -07:00
proper citations . The citations follow [ citation : source_id ] format using source IDs from the
2025-06-04 23:09:31 -07:00
documents . If no documents are provided , it will use chat history to generate
an answer .
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
Returns :
Dict containing the final answer in the " final_answer " key .
"""
2025-07-06 17:51:24 -07:00
from app . services . llm_service import get_user_fast_llm
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
# Get configuration and relevant documents from configuration
configuration = Configuration . from_runnable_config ( config )
2025-06-05 20:33:09 -07:00
documents = state . reranked_documents
2025-06-03 00:10:35 -07:00
user_query = configuration . user_query
2025-06-09 15:50:15 -07:00
user_id = configuration . user_id
2025-10-10 00:50:29 -07:00
search_space_id = configuration . search_space_id
2025-10-12 20:15:27 -07:00
language = configuration . language
2025-06-09 15:50:15 -07:00
# Get user's fast LLM
2025-10-10 00:50:29 -07:00
llm = await get_user_fast_llm ( state . db_session , user_id , search_space_id )
2025-06-09 15:50:15 -07:00
if not llm :
2025-10-10 00:50:29 -07:00
error_message = f " No fast LLM configured for user { user_id } in search space { search_space_id } "
2025-06-09 15:50:15 -07:00
print ( error_message )
raise RuntimeError ( error_message )
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Determine if we have documents and optimize for token limits
has_documents_initially = documents and len ( documents ) > 0
2025-08-27 18:43:33 -07:00
chat_history_str = langchain_chat_history_to_str ( state . chat_history )
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
if has_documents_initially :
# Create base message template for token calculation (without documents)
base_human_message_template = f """
User ' s question:
< user_query >
{ user_query }
< / user_query >
2025-06-03 00:10:35 -07:00
2025-06-05 20:33:09 -07:00
Please provide a detailed , comprehensive answer to the user ' s question using the information from their personal knowledge sources. Make sure to cite all information appropriately and engage in a conversational manner.
2025-06-03 00:10:35 -07:00
"""
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Use initial system prompt for token calculation
2025-10-12 20:15:27 -07:00
initial_system_prompt = get_qna_citation_system_prompt (
chat_history_str , language
)
2025-07-24 14:43:48 -07:00
base_messages = [
2025-06-05 20:33:09 -07:00
SystemMessage ( content = initial_system_prompt ) ,
2025-07-25 15:11:19 -07:00
HumanMessage ( content = base_human_message_template ) ,
2025-06-05 20:33:09 -07:00
]
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Optimize documents to fit within token limits
2025-07-25 15:11:19 -07:00
optimized_documents , has_optimized_documents = (
optimize_documents_for_token_limit ( documents , base_messages , llm . model )
2025-06-05 20:33:09 -07:00
)
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Update state based on optimization result
documents = optimized_documents
has_documents = has_optimized_documents
else :
has_documents = False
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Choose system prompt based on final document availability
2025-07-25 15:11:19 -07:00
system_prompt = (
2025-10-12 13:13:42 +05:30
get_qna_citation_system_prompt ( chat_history_str , language )
2025-07-25 15:11:19 -07:00
if has_documents
2025-10-12 13:13:42 +05:30
else get_qna_no_documents_system_prompt ( chat_history_str , language )
2025-07-25 15:11:19 -07:00
)
2025-06-05 20:33:09 -07:00
# Generate documents section
2025-07-25 15:11:19 -07:00
documents_text = (
format_documents_section (
documents , " Source material from your personal knowledge base "
)
if has_documents
else " "
)
2025-06-05 20:33:09 -07:00
# Create final human message content
instruction_text = (
" Please provide a detailed, comprehensive answer to the user ' s question using the information from their personal knowledge sources. Make sure to cite all information appropriately and engage in a conversational manner. "
2025-07-25 15:11:19 -07:00
if has_documents
else " Please provide a helpful answer to the user ' s question based on our conversation history and your general knowledge. Engage in a conversational manner. "
2025-06-05 20:33:09 -07:00
)
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
human_message_content = f """
2025-06-04 23:09:31 -07:00
{ documents_text }
2025-06-03 00:10:35 -07:00
User ' s question:
< user_query >
{ user_query }
< / user_query >
2025-06-05 20:33:09 -07:00
{ instruction_text }
2025-06-03 00:10:35 -07:00
"""
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Create final messages for the LLM
2025-07-24 14:43:48 -07:00
messages_with_chat_history = [
2025-06-04 23:09:31 -07:00
SystemMessage ( content = system_prompt ) ,
2025-07-25 15:11:19 -07:00
HumanMessage ( content = human_message_content ) ,
2025-06-03 00:10:35 -07:00
]
2025-07-25 15:11:19 -07:00
2025-06-05 20:33:09 -07:00
# Log final token count
2025-06-09 15:50:15 -07:00
total_tokens = calculate_token_count ( messages_with_chat_history , llm . model )
2025-06-05 20:33:09 -07:00
print ( f " Final token count: { total_tokens } " )
2025-07-25 15:11:19 -07:00
2025-06-03 00:10:35 -07:00
# Call the LLM and get the response
response = await llm . ainvoke ( messages_with_chat_history )
final_answer = response . content
2025-07-25 15:11:19 -07:00
return { " final_answer " : final_answer }