Spaces:
Sleeping
Sleeping
on going
Browse files- .gitignore +2 -3
- app.py +76 -53
- doc_preproc.py +124 -0
- requirements.txt +6 -1
.gitignore
CHANGED
|
@@ -10,10 +10,9 @@ __pycache__/
|
|
| 10 |
*.code-workspace
|
| 11 |
.DS_Store
|
| 12 |
Thumbs.db
|
| 13 |
-
*.log
|
| 14 |
*.tmp
|
| 15 |
faiss_index/
|
| 16 |
ConvertedFiles/
|
| 17 |
OrigFolder/
|
| 18 |
-
TempFolder/
|
| 19 |
-
*.faiss
|
|
|
|
| 10 |
*.code-workspace
|
| 11 |
.DS_Store
|
| 12 |
Thumbs.db
|
| 13 |
+
conversion_log_*.log
|
| 14 |
*.tmp
|
| 15 |
faiss_index/
|
| 16 |
ConvertedFiles/
|
| 17 |
OrigFolder/
|
| 18 |
+
TempFolder/
|
|
|
app.py
CHANGED
|
@@ -5,21 +5,28 @@ from langchain_community.vectorstores import FAISS
|
|
| 5 |
from langchain_community.document_loaders import PyPDFDirectoryLoader, WebBaseLoader
|
| 6 |
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 7 |
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 8 |
-
from
|
| 9 |
from langchain.tools.retriever import create_retriever_tool
|
| 10 |
from langchain.agents import create_tool_calling_agent, AgentExecutor
|
| 11 |
-
from langchain_core.tools import tool
|
| 12 |
import gradio as gr
|
| 13 |
import numpy as np
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
#
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
def load_web_resources(file='websites.txt'):
|
| 25 |
urls = ['https://steveblank.com/']
|
|
@@ -31,54 +38,41 @@ def load_web_resources(file='websites.txt'):
|
|
| 31 |
try:
|
| 32 |
loader = WebBaseLoader(url)
|
| 33 |
web_docs.extend(loader.load())
|
|
|
|
| 34 |
except Exception as e:
|
| 35 |
-
|
| 36 |
return web_docs
|
| 37 |
|
| 38 |
def build_vectorstore(pdf_dir="ConvertedFiles"):
|
| 39 |
-
loader = PyPDFDirectoryLoader(pdf_dir)
|
| 40 |
-
docs = loader.load()
|
| 41 |
-
web_docs = load_web_resources()
|
| 42 |
-
all_docs = docs + web_docs
|
| 43 |
-
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
|
| 44 |
-
splits = splitter.split_documents(all_docs)
|
| 45 |
-
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
|
| 46 |
-
vectorstore = FAISS.from_documents(splits, embeddings)
|
| 47 |
-
vectorstore.save_local("faiss_index")
|
| 48 |
-
return vectorstore
|
| 49 |
-
|
| 50 |
-
@tool
|
| 51 |
-
def scenario_simulator(params: dict) -> str:
|
| 52 |
-
"""Simulates product adoption using Monte Carlo. Params: initial_users, growth_rate, time_months, uncertainty."""
|
| 53 |
try:
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
return f"Predicted users after {time_months} months: {mean:.0f} ± {std:.0f} (mean ± std, {runs} runs)."
|
| 68 |
except Exception as e:
|
| 69 |
-
|
|
|
|
| 70 |
|
| 71 |
def setup_agent(vectorstore):
|
| 72 |
retriever_tool = create_retriever_tool(
|
| 73 |
vectorstore.as_retriever(search_kwargs={"k": 5}),
|
| 74 |
"course_materials_retriever",
|
| 75 |
-
"Searches
|
| 76 |
)
|
| 77 |
-
web_tool =
|
| 78 |
tools = [retriever_tool, web_tool, scenario_simulator]
|
| 79 |
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
|
| 80 |
prompt = ChatPromptTemplate.from_messages([
|
| 81 |
-
("system", "You are Dr. Sue Cohen
|
| 82 |
("human", "{input}"),
|
| 83 |
MessagesPlaceholder("agent_scratchpad")
|
| 84 |
])
|
|
@@ -87,20 +81,47 @@ def setup_agent(vectorstore):
|
|
| 87 |
return executor
|
| 88 |
|
| 89 |
def chat(message, history):
|
| 90 |
-
# Parse query for scenario_simulator
|
| 91 |
if "initial_users" in message:
|
| 92 |
params = {}
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
if not os.path.exists("faiss_index"):
|
|
|
|
| 102 |
vectorstore = build_vectorstore()
|
| 103 |
else:
|
|
|
|
| 104 |
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
|
| 105 |
vectorstore = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
|
| 106 |
|
|
@@ -108,9 +129,11 @@ agent = setup_agent(vectorstore)
|
|
| 108 |
|
| 109 |
demo = gr.ChatInterface(
|
| 110 |
fn=chat,
|
|
|
|
| 111 |
title="COMMERCIALIZING NEW TECHNOLOGIES RAG Bot",
|
| 112 |
-
description="Query materials
|
| 113 |
)
|
| 114 |
|
| 115 |
if __name__ == "__main__":
|
| 116 |
-
|
|
|
|
|
|
| 5 |
from langchain_community.document_loaders import PyPDFDirectoryLoader, WebBaseLoader
|
| 6 |
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 7 |
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 8 |
+
from langchain_tavily import TavilySearch
|
| 9 |
from langchain.tools.retriever import create_retriever_tool
|
| 10 |
from langchain.agents import create_tool_calling_agent, AgentExecutor
|
|
|
|
| 11 |
import gradio as gr
|
| 12 |
import numpy as np
|
| 13 |
+
import logging
|
| 14 |
+
from pythia import scenario_simulator
|
| 15 |
|
| 16 |
+
# Set up logging
|
| 17 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
# Load environment variables
|
| 21 |
+
load_dotenv()
|
| 22 |
+
# os.environ['USER_AGENT'] = os.getenv('USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
|
| 23 |
+
|
| 24 |
+
# # Verify API keys
|
| 25 |
+
# openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 26 |
+
# tavily_api_key = os.getenv("TAVILY_API_KEY")
|
| 27 |
+
# if not openai_api_key or not tavily_api_key:
|
| 28 |
+
# logger.error("Missing API keys: OPENAI_API_KEY or TAVILY_API_KEY")
|
| 29 |
+
# raise ValueError("API keys not set in environment")
|
| 30 |
|
| 31 |
def load_web_resources(file='websites.txt'):
|
| 32 |
urls = ['https://steveblank.com/']
|
|
|
|
| 38 |
try:
|
| 39 |
loader = WebBaseLoader(url)
|
| 40 |
web_docs.extend(loader.load())
|
| 41 |
+
logger.info(f"Loaded web resources from {url}")
|
| 42 |
except Exception as e:
|
| 43 |
+
logger.error(f"Failed to load {url}: {e}")
|
| 44 |
return web_docs
|
| 45 |
|
| 46 |
def build_vectorstore(pdf_dir="ConvertedFiles"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
try:
|
| 48 |
+
loader = PyPDFDirectoryLoader(pdf_dir)
|
| 49 |
+
docs = loader.load()
|
| 50 |
+
logger.info(f"Loaded {len(docs)} documents from {pdf_dir}")
|
| 51 |
+
web_docs = load_web_resources()
|
| 52 |
+
all_docs = docs + web_docs
|
| 53 |
+
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
|
| 54 |
+
splits = splitter.split_documents(all_docs)
|
| 55 |
+
logger.info(f"Split into {len(splits)} document chunks")
|
| 56 |
+
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
|
| 57 |
+
vectorstore = FAISS.from_documents(splits, embeddings)
|
| 58 |
+
vectorstore.save_local("faiss_index")
|
| 59 |
+
logger.info("Saved FAISS index to faiss_index/")
|
| 60 |
+
return vectorstore
|
|
|
|
| 61 |
except Exception as e:
|
| 62 |
+
logger.error(f"Failed to build vectorstore: {e}")
|
| 63 |
+
raise
|
| 64 |
|
| 65 |
def setup_agent(vectorstore):
|
| 66 |
retriever_tool = create_retriever_tool(
|
| 67 |
vectorstore.as_retriever(search_kwargs={"k": 5}),
|
| 68 |
"course_materials_retriever",
|
| 69 |
+
"Searches course materials on optimization, simulation, decision tech, project management, and lean startup."
|
| 70 |
)
|
| 71 |
+
web_tool = TavilySearch(max_results=3)
|
| 72 |
tools = [retriever_tool, web_tool, scenario_simulator]
|
| 73 |
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
|
| 74 |
prompt = ChatPromptTemplate.from_messages([
|
| 75 |
+
("system", "You are Dr. Sue Cohen, a passionate professor of Commercializing New Technologies. Answer in a witty, professional, detailed style (100-200 words for course content), using main points from course materials without citing file names in the main response. Optionally include PDF titles as references at the end (e.g., 'Reference: Lecture_5.pdf'). Always use Tavily/web for recent updates in separate paragraphs, citing URLs. Don’t mix course and web content in the same paragraph. Run scenario sims for adoption/scalability with clear params. Redirect off-topic queries, prompt next steps with a sharp question."),
|
| 76 |
("human", "{input}"),
|
| 77 |
MessagesPlaceholder("agent_scratchpad")
|
| 78 |
])
|
|
|
|
| 81 |
return executor
|
| 82 |
|
| 83 |
def chat(message, history):
|
| 84 |
+
# Parse query for scenario_simulator
|
| 85 |
if "initial_users" in message:
|
| 86 |
params = {}
|
| 87 |
+
try:
|
| 88 |
+
for pair in message.split(","):
|
| 89 |
+
key, value = pair.split(":")
|
| 90 |
+
key = key.strip()
|
| 91 |
+
value = float(value.strip().replace("%", "")) / 100 if "%" in value else int(value.strip())
|
| 92 |
+
params[key] = value
|
| 93 |
+
logger.info(f"Running scenario simulator with params: {params}")
|
| 94 |
+
return scenario_simulator(params)
|
| 95 |
+
except Exception as e:
|
| 96 |
+
logger.error(f"Failed to parse scenario params: {e}")
|
| 97 |
+
return f"Error parsing params: {e}. Format: initial_users:1000, growth_rate:5%, time_months:12, uncertainty:10%"
|
| 98 |
+
|
| 99 |
+
# Retrieve documents
|
| 100 |
+
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
|
| 101 |
+
docs = retriever.get_relevant_documents(message)
|
| 102 |
+
context = "\n\n".join([doc.page_content for doc in docs])
|
| 103 |
+
titles = [os.path.basename(doc.metadata.get('source', 'Unknown')) for doc in docs]
|
| 104 |
+
logger.info(f"Retrieved {len(docs)} documents for query: {message}")
|
| 105 |
+
|
| 106 |
+
# Always invoke Tavily for updates
|
| 107 |
+
try:
|
| 108 |
+
web_results = TavilySearch(max_results=3).invoke(message)
|
| 109 |
+
web_context = "\n".join([f"Web: {res.get('title', 'Untitled')} - {res.get('content', '')[:150]}... (URL: {res.get('url', 'Unknown')})" for res in web_results])
|
| 110 |
+
logger.info(f"Retrieved {len(web_results)} web results from Tavily")
|
| 111 |
+
except Exception as e:
|
| 112 |
+
web_context = ""
|
| 113 |
+
logger.error(f"Tavily search failed: {e}")
|
| 114 |
+
|
| 115 |
+
# Combine contexts
|
| 116 |
+
input_with_context = f"{message}\n\nCourse Materials:\n{context}\n\nWeb Updates:\n{web_context}\n\nPDF References:\n{', '.join(titles)}"
|
| 117 |
+
logger.info(f"Processing query: {message}")
|
| 118 |
+
return agent.invoke({"input": input_with_context})["output"]
|
| 119 |
|
| 120 |
if not os.path.exists("faiss_index"):
|
| 121 |
+
logger.warning("FAISS index not found, rebuilding...")
|
| 122 |
vectorstore = build_vectorstore()
|
| 123 |
else:
|
| 124 |
+
logger.info("Loading prebuilt FAISS index...")
|
| 125 |
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
|
| 126 |
vectorstore = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
|
| 127 |
|
|
|
|
| 129 |
|
| 130 |
demo = gr.ChatInterface(
|
| 131 |
fn=chat,
|
| 132 |
+
chatbot=gr.Chatbot(type='messages'),
|
| 133 |
title="COMMERCIALIZING NEW TECHNOLOGIES RAG Bot",
|
| 134 |
+
description="Query course materials with web augmentation"
|
| 135 |
)
|
| 136 |
|
| 137 |
if __name__ == "__main__":
|
| 138 |
+
logger.info("Starting Gradio app...")
|
| 139 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
doc_preproc.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import sys
|
| 4 |
+
import logging
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
# Set up logging
|
| 8 |
+
logging.basicConfig(
|
| 9 |
+
filename=f"conversion_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log",
|
| 10 |
+
level=logging.INFO,
|
| 11 |
+
format="%(asctime)s - %(levelname)s - %(message)s"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
# For DOC/DOCX conversion
|
| 15 |
+
try:
|
| 16 |
+
from docx2pdf import convert as docx_convert
|
| 17 |
+
except ImportError:
|
| 18 |
+
logging.error("docx2pdf not installed. Run 'pip install docx2pdf'.")
|
| 19 |
+
sys.exit(1)
|
| 20 |
+
|
| 21 |
+
# For Word-based conversion
|
| 22 |
+
try:
|
| 23 |
+
import win32com.client
|
| 24 |
+
except ImportError:
|
| 25 |
+
logging.error("pywin32 not installed. Run 'pip install pywin32'.")
|
| 26 |
+
sys.exit(1)
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
import comtypes.client
|
| 30 |
+
except ImportError:
|
| 31 |
+
logging.warning("comtypes not installed. Run 'pip install comtypes' for fallback.")
|
| 32 |
+
|
| 33 |
+
def check_irm_status(filepath):
|
| 34 |
+
"""Check if IRM is enabled on a DOC/DOCX file."""
|
| 35 |
+
try:
|
| 36 |
+
word_app = win32com.client.Dispatch("Word.Application")
|
| 37 |
+
doc = word_app.Documents.Open(os.path.abspath(filepath), ReadOnly=True, Visible=False)
|
| 38 |
+
irm_enabled = doc.Permission.Enabled
|
| 39 |
+
doc.Close()
|
| 40 |
+
word_app.Quit()
|
| 41 |
+
return irm_enabled
|
| 42 |
+
except Exception as e:
|
| 43 |
+
logging.error(f"Failed to check IRM for {filepath}: {e}")
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
def convert_docx_docx2pdf(filepath, output_pdf):
|
| 47 |
+
"""Attempt DOC/DOCX to PDF conversion using docx2pdf."""
|
| 48 |
+
try:
|
| 49 |
+
docx_convert(filepath, output_pdf)
|
| 50 |
+
logging.info(f"Converted {filepath} to {output_pdf} using docx2pdf.")
|
| 51 |
+
return True
|
| 52 |
+
except Exception as e:
|
| 53 |
+
logging.error(f"docx2pdf failed for {filepath}: {e}")
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
def convert_docx_win32(filepath, output_pdf):
|
| 57 |
+
"""Attempt DOC/DOCX to PDF conversion using win32com SaveAs."""
|
| 58 |
+
try:
|
| 59 |
+
word_app = win32com.client.Dispatch("Word.Application")
|
| 60 |
+
doc = word_app.Documents.Open(os.path.abspath(filepath), ReadOnly=True, Visible=False)
|
| 61 |
+
doc.SaveAs(output_pdf, FileFormat=17) # 17 = wdFormatPDF
|
| 62 |
+
doc.Close()
|
| 63 |
+
word_app.Quit()
|
| 64 |
+
logging.info(f"Converted {filepath} to {output_pdf} using win32com SaveAs.")
|
| 65 |
+
return True
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logging.error(f"win32com SaveAs failed for {filepath}: {e}")
|
| 68 |
+
return False
|
| 69 |
+
|
| 70 |
+
def convert_docx_comtypes(filepath, output_pdf):
|
| 71 |
+
"""Attempt DOC/DOCX to PDF conversion using comtypes SaveAs."""
|
| 72 |
+
try:
|
| 73 |
+
word_app = comtypes.client.CreateObject("Word.Application")
|
| 74 |
+
doc = word_app.Documents.Open(os.path.abspath(filepath), ReadOnly=True, Visible=False)
|
| 75 |
+
doc.SaveAs(output_pdf, FileFormat=17) # 17 = wdFormatPDF
|
| 76 |
+
doc.Close()
|
| 77 |
+
word_app.Quit()
|
| 78 |
+
logging.info(f"Converted {filepath} to {output_pdf} using comtypes SaveAs.")
|
| 79 |
+
return True
|
| 80 |
+
except Exception as e:
|
| 81 |
+
logging.error(f"comtypes SaveAs failed for {filepath}: {e}")
|
| 82 |
+
return False
|
| 83 |
+
|
| 84 |
+
def main(input_dir, output_dir):
|
| 85 |
+
if not os.path.isdir(input_dir):
|
| 86 |
+
logging.error(f"{input_dir} is not a directory.")
|
| 87 |
+
sys.exit(1)
|
| 88 |
+
|
| 89 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 90 |
+
|
| 91 |
+
for root, _, files in os.walk(input_dir):
|
| 92 |
+
rel_dir = os.path.relpath(root, input_dir)
|
| 93 |
+
target_subdir = os.path.join(output_dir, rel_dir)
|
| 94 |
+
os.makedirs(target_subdir, exist_ok=True)
|
| 95 |
+
|
| 96 |
+
for file in files:
|
| 97 |
+
filepath = os.path.join(root, file)
|
| 98 |
+
base, ext = os.path.splitext(file)
|
| 99 |
+
output_pdf = os.path.join(target_subdir, base + '.pdf')
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
if ext.lower() in ['.doc', '.docx']:
|
| 103 |
+
# Check IRM status
|
| 104 |
+
irm_status = check_irm_status(filepath)
|
| 105 |
+
if irm_status is not None:
|
| 106 |
+
logging.info(f"IRM enabled for {filepath}: {irm_status}")
|
| 107 |
+
|
| 108 |
+
# Try docx2pdf first (simpler, faster)
|
| 109 |
+
if not convert_docx_docx2pdf(filepath, output_pdf):
|
| 110 |
+
# Fallback to win32com
|
| 111 |
+
if not convert_docx_win32(filepath, output_pdf):
|
| 112 |
+
# Fallback to comtypes if available
|
| 113 |
+
if 'comtypes' in sys.modules and not convert_docx_comtypes(filepath, output_pdf):
|
| 114 |
+
logging.warning(f"Failed to convert {filepath}. Likely IRM or strict restrictions.")
|
| 115 |
+
elif not 'comtypes' in sys.modules:
|
| 116 |
+
logging.warning(f"Failed to convert {filepath}. Install comtypes for additional fallback.")
|
| 117 |
+
elif ext.lower() == '.pdf':
|
| 118 |
+
shutil.copy(filepath, output_pdf)
|
| 119 |
+
logging.info(f"Copied {filepath} to {output_pdf}")
|
| 120 |
+
except Exception as e:
|
| 121 |
+
logging.error(f"Error processing {filepath}: {e}")
|
| 122 |
+
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
main("ToAdd", "ConvertedFiles")
|
requirements.txt
CHANGED
|
@@ -8,4 +8,9 @@ python-dotenv>=1.0.1
|
|
| 8 |
langchain-text-splitters>=0.3.0
|
| 9 |
tavily-python>=0.4.0
|
| 10 |
numpy>=1.26.4
|
| 11 |
-
beautifulsoup4>=4.12.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
langchain-text-splitters>=0.3.0
|
| 9 |
tavily-python>=0.4.0
|
| 10 |
numpy>=1.26.4
|
| 11 |
+
beautifulsoup4>=4.12.0
|
| 12 |
+
docx2pdf>=0.2.2
|
| 13 |
+
pywin32>=306
|
| 14 |
+
comtypes>=1.4.7
|
| 15 |
+
pypdf>=5.0.0
|
| 16 |
+
comtypes>=1.4.7
|