Files
patchy631--ai-engineering-hub/agentic_rag/app.py
T
2026-07-13 12:37:47 +08:00

187 lines
6.7 KiB
Python

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"""
<iframe
src="data:application/pdf;base64,{base64_pdf}"
width="100%"
height="600px"
type="application/pdf"
>
</iframe>
"""
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 <img src="data:image/png;base64,{}" width="120" style="vertical-align: -3px;">
""".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})