import streamlit as st import os import tempfile import gc import base64 import time from crewai import Agent, Crew, Process, Task from crewai_tools import SerperDevTool from src.agentic_rag.tools.custom_tool import DocumentSearchTool # =========================== # Define Agents & Tasks # =========================== def create_agents_and_tasks(pdf_tool): """Creates a Crew with the given PDF tool (if any) and a web search tool.""" web_search_tool = SerperDevTool() retriever_agent = Agent( role="Retrieve relevant information to answer the user query: {query}", goal=( "Retrieve the most relevant information from the available sources " "for the user query: {query}. Always try to use the PDF search tool first. " "If you are not able to retrieve the information from the PDF search tool, " "then try to use the web search tool." ), backstory=( "You're a meticulous analyst with a keen eye for detail. " "You're known for your ability to understand user queries: {query} " "and retrieve knowledge from the most suitable knowledge base." ), verbose=True, tools=[t for t in [pdf_tool, web_search_tool] if t], ) response_synthesizer_agent = Agent( role="Response synthesizer agent for the user query: {query}", goal=( "Synthesize the retrieved information into a concise and coherent response " "based on the user query: {query}. If you are not able to retrieve the " 'information then respond with "I\'m sorry, I couldn\'t find the information ' 'you\'re looking for."' ), backstory=( "You're a skilled communicator with a knack for turning " "complex information into clear and concise responses." ), verbose=True ) retrieval_task = Task( description=( "Retrieve the most relevant information from the available " "sources for the user query: {query}" ), expected_output=( "The most relevant information in the form of text as retrieved " "from the sources." ), agent=retriever_agent ) response_task = Task( description="Synthesize the final response for the user query: {query}", expected_output=( "A concise and coherent response based on the retrieved information " "from the right source for the user query: {query}. If you are not " "able to retrieve the information, then respond with: " '"I\'m sorry, I couldn\'t find the information you\'re looking for."' ), agent=response_synthesizer_agent ) crew = Crew( agents=[retriever_agent, response_synthesizer_agent], tasks=[retrieval_task, response_task], process=Process.sequential, # or Process.hierarchical verbose=True ) return crew # =========================== # Streamlit Setup # =========================== if "messages" not in st.session_state: st.session_state.messages = [] # Chat history if "pdf_tool" not in st.session_state: st.session_state.pdf_tool = None # Store the DocumentSearchTool if "crew" not in st.session_state: st.session_state.crew = None # Store the Crew object def reset_chat(): st.session_state.messages = [] gc.collect() def display_pdf(file_bytes: bytes, file_name: str): """Displays the uploaded PDF in an iframe.""" base64_pdf = base64.b64encode(file_bytes).decode("utf-8") pdf_display = f""" """ st.markdown(f"### Preview of {file_name}") st.markdown(pdf_display, unsafe_allow_html=True) # =========================== # Sidebar # =========================== with st.sidebar: st.header("Add Your PDF Document") uploaded_file = st.file_uploader("Choose a PDF file", type=["pdf"]) if uploaded_file is not None: # If there's a new file and we haven't set pdf_tool yet... if st.session_state.pdf_tool is None: with tempfile.TemporaryDirectory() as temp_dir: temp_file_path = os.path.join(temp_dir, uploaded_file.name) with open(temp_file_path, "wb") as f: f.write(uploaded_file.getvalue()) with st.spinner("Indexing PDF... Please wait..."): st.session_state.pdf_tool = DocumentSearchTool(file_path=temp_file_path) st.success("PDF indexed! Ready to chat.") # Optionally display the PDF in the sidebar display_pdf(uploaded_file.getvalue(), uploaded_file.name) st.button("Clear Chat", on_click=reset_chat) # =========================== # Main Chat Interface # =========================== st.markdown(""" # Agentic RAG powered by """.format(base64.b64encode(open("assets/crewai.png", "rb").read()).decode()), unsafe_allow_html=True) # Render existing conversation for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # Chat input prompt = st.chat_input("Ask a question about your PDF...") if prompt: # 1. Show user message immediately st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) # 2. Build or reuse the Crew (only once after PDF is loaded) if st.session_state.crew is None: st.session_state.crew = create_agents_and_tasks(st.session_state.pdf_tool) # 3. Get the response with st.chat_message("assistant"): message_placeholder = st.empty() full_response = "" # Get the complete response first with st.spinner("Thinking..."): inputs = {"query": prompt} result = st.session_state.crew.kickoff(inputs=inputs).raw # Split by lines first to preserve code blocks and other markdown lines = result.split('\n') for i, line in enumerate(lines): full_response += line if i < len(lines) - 1: # Don't add newline to the last line full_response += '\n' message_placeholder.markdown(full_response + "▌") time.sleep(0.15) # Adjust the speed as needed # Show the final response without the cursor message_placeholder.markdown(full_response) # 4. Save assistant's message to session st.session_state.messages.append({"role": "assistant", "content": result})