erokou commited on
Commit
61cb8cf
·
1 Parent(s): 8d6afb6
Files changed (4) hide show
  1. .gitignore +2 -3
  2. app.py +76 -53
  3. doc_preproc.py +124 -0
  4. 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 langchain_community.tools.tavily_search import TavilySearchResults
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
- #to run locally
16
- # load_dotenv()
17
- os.environ['USER_AGENT'] = os.getenv('USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
18
- # Load secrets from Hugging Face environment
19
- openai_api_key = os.getenv("OPENAI_API_KEY")
20
- tavily_api_key = os.getenv("TAVILY_API_KEY")
21
- if not openai_api_key or not tavily_api_key:
22
- raise ValueError("API keys not set in Hugging Face Secrets")
 
 
 
 
 
 
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
- print(f"Failed to load {url}: {e}")
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
- initial_users = params.get("initial_users", 1000)
55
- growth_rate = params.get("growth_rate", 0.05) # 5%/month
56
- time_months = params.get("time_months", 12)
57
- uncertainty = params.get("uncertainty", 0.1) # 10% variance
58
- runs = 1000
59
- final_users = []
60
- for _ in range(runs):
61
- users = initial_users
62
- for _ in range(time_months):
63
- users *= (1 + np.random.normal(growth_rate, uncertainty))
64
- final_users.append(users)
65
- mean = np.mean(final_users)
66
- std = np.std(final_users)
67
- return f"Predicted users after {time_months} months: {mean:.0f} ± {std:.0f} (mean ± std, {runs} runs)."
68
  except Exception as e:
69
- return f"Sim error: {e}. Check params: {params}"
 
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 PDFs and crawled web resources on optimization/sim/decision tech/project mgmt/lean startup."
76
  )
77
- web_tool = TavilySearchResults(max_results=3)
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's Teaching Assistant, a passionate tutor for Commercializing New Technologies, Leading Organizations to Innovate Smarter, Competing in Emerging Economies, and Strategic Management. Guide users with sharp insights, probing questions, and hints from course materials/web. Be witty (e.g., 'Solid, but don’t optimize for chaos—how’s this tie to diffusion?'), professional, encouraging. Run scenario sims for adoption/scalability queries with clear params. Prioritize course/web data, use web for updates. Be concise, cite sources, never solve outright, redirect if off-topic, prompt next steps."),
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 if needed
91
  if "initial_users" in message:
92
  params = {}
93
- for pair in message.split(","):
94
- key, value = pair.split(":")
95
- key = key.strip()
96
- value = float(value.strip().replace("%", "")) / 100 if "%" in value else int(value.strip())
97
- params[key] = value
98
- return scenario_simulator(params)
99
- return agent.invoke({"input": message})["output"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 + web augmented"
113
  )
114
 
115
  if __name__ == "__main__":
116
- demo.launch(server_name="0.0.0.0", server_port=7860,share=True)
 
 
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