File size: 6,570 Bytes
52ea0db 6bbd22d 2359e0b 52ea0db f7831ce 52ea0db b269ca5 52ea0db b269ca5 52ea0db 4c82f28 2359e0b 4c82f28 2359e0b 4c82f28 52ea0db 6bbd22d 52ea0db 01d9bfd 2177008 52ea0db 01d9bfd 52ea0db 6bbd22d 52ea0db 01d9bfd 52ea0db 1add8ee 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db 01d9bfd 52ea0db b269ca5 01d9bfd b269ca5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
import os
print(">>> DEBUG: Environment Variables at Startup <<<")
for var in ("OPENAI_API_KEY", "LLAMA_CLOUD_API_KEY"):
#, "LLAMA_CLOUD_BASE_URL"):
print(f"{var} = {os.getenv(var)!r}")
# import openai
import shutil
import asyncio
from pathlib import Path
import nest_asyncio
nest_asyncio.apply()
import gradio as gr
from PyPDF2 import PdfReader # pip install PyPDF2
from llama_parse import LlamaParse
from llama_index.core import (
Settings, VectorStoreIndex, StorageContext, load_index_from_storage
)
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.tools import QueryEngineTool
from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.workflow import Context
# ---- 1. Global Settings & API Keys ----
global OPENAI_API_KEY, LLAMA_CLOUD_API_KEY
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
# openai.api_key = OPENAI_API_KEY
Settings.llm = OpenAI(model="gpt-4o")
Settings.embed_model = OpenAIEmbedding(model_name="text-embedding-3-large")
Settings.chunk_size = 512
Settings.chunk_overlap = 64
# ---- 2. Parser Setup ----
print(">>> DEBUG: About to init LlamaParse with key:", os.getenv("LLAMA_CLOUD_API_KEY") is not None)
# print(">>> DEBUG: About to init LlamaParse with key:", os.getenv("LLAMA_CLOUD_BASE_URL") is not None)
parser = LlamaParse(
api_key = LLAMA_CLOUD_API_KEY,
# base_url = os.getenv("LLAMA_CLOUD_BASE_URL"),
result_type = "markdown",
content_guideline_instruction = (
"You are processing a PDF slide deck. "
"Produce Markdown with slide metadata, cleaned bullets, tables, "
"charts summaries, figures captions, metrics, and a 1β2 sentence takeaway."
),
verbose=True
)
# Ensure directories exist
Path("./user_data").mkdir(exist_ok=True)
Path("./index_data").mkdir(exist_ok=True)
# ---- 3a. Upload + Answer Logic ----
async def answer(uploaded_files: list[gr.FileData], question: str) -> str:
print(f">>> DEBUG: answer() called. OPENAI key set? {os.getenv('OPENAI_API_KEY') is not None}")
print(f">>> DEBUG: answer() called. LLAMA key set? {os.getenv('LLAMA_CLOUD_API_KEY') is not None}")
if not uploaded_files:
return "β Please upload at least one PDF."
if len(uploaded_files) > 5:
return "β You can upload up to 5 PDF files."
tools = []
for file_obj in uploaded_files:
# 1) Page-count check
try:
reader = PdfReader(file_obj.name)
except Exception as e:
return f"β Error reading {file_obj.name}: {e}"
if len(reader.pages) > 50:
return f"β {Path(file_obj.name).name} has {len(reader.pages)} pages (>50)."
# 2) Copy PDF into user_data
dest = Path("./user_data") / Path(file_obj.name).name
shutil.copyfile(file_obj.name, dest)
# 3) Parse via LlamaParse
docs = parser.load_data(dest)
# 4) Index folder per file stem
stem = dest.stem
idx_dir = Path(f"./index_data/{stem}")
# 5) Load or build index
if idx_dir.exists() and any(idx_dir.iterdir()):
sc = StorageContext.from_defaults(persist_dir=str(idx_dir))
idx = load_index_from_storage(sc)
else:
sc = StorageContext.from_defaults()
idx = VectorStoreIndex.from_documents(docs, storage_context=sc)
sc.persist(persist_dir=str(idx_dir))
# 6) Wrap in QueryEngineTool
tools.append(
QueryEngineTool.from_defaults(
query_engine=idx.as_query_engine(),
name=f"vector_index_{stem}",
description=f"Query engine for {stem}.pdf"
)
)
# 7) Combine tools into SubQuestionQueryEngine + Agent
subq = SubQuestionQueryEngine.from_defaults(query_engine_tools=tools)
tools.append(
QueryEngineTool.from_defaults(
query_engine=subq,
name="sub_question_query_engine",
description="Multi-file comparative queries"
)
)
agent = FunctionAgent(tools=tools, llm=OpenAI(model="gpt-4o"))
ctx = Context(agent)
# 8) Run agent
resp = await agent.run(question, ctx=ctx)
return str(resp)
# ---- 3b. Remove Documents Logic ----
def remove_docs(filenames: str) -> str:
"""
filenames: comma-separated list of exact PDF filenames (with .pdf)
Deletes each from ./user_data/ and its index folder under ./index_data/
"""
if not filenames.strip():
return "β Enter at least one filename to remove."
removed, not_found = [], []
for name in [f.strip() for f in filenames.split(",")]:
pdf_path = Path("./user_data") / name
idx_path = Path("./index_data") / Path(name).stem
ok = True
if pdf_path.exists():
pdf_path.unlink()
else:
ok = False
if idx_path.exists():
shutil.rmtree(idx_path)
else:
ok = ok and False
if ok:
removed.append(name)
else:
not_found.append(name)
msg = ""
if removed:
msg += f"β
Removed: {', '.join(removed)}.\n"
if not_found:
msg += f"β οΈ Not found: {', '.join(not_found)}."
return msg.strip()
# ---- 4. Gradio UI ----
with gr.Blocks() as demo:
gr.Markdown("# π PDF Slide Deck Q&A Bot")
with gr.Tab("Ask Questions"):
with gr.Row():
file_input = gr.UploadButton(
"Upload up to 5 PDFs",
file_types=[".pdf"],
file_count="multiple"
)
question = gr.Textbox(
lines=2,
placeholder="Ask your question about the uploaded slide decks..."
)
output = gr.Textbox(label="Answer")
ask_btn = gr.Button("Ask")
ask_btn.click(
fn=answer,
inputs=[file_input, question],
outputs=output
)
with gr.Tab("Remove Documents"):
remove_input = gr.Textbox(
lines=1,
placeholder="e.g. Q1-Slides.pdf, Q2-Slides.pdf"
)
remove_output = gr.Textbox(label="Removal Status")
remove_btn = gr.Button("Remove Docs")
remove_btn.click(
fn=remove_docs,
inputs=remove_input,
outputs=remove_output
)
if __name__ == "__main__":
demo.launch()
|