WaysAheadGlobal commited on
Commit
243d152
Β·
verified Β·
1 Parent(s): 6f01198

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -157
app.py CHANGED
@@ -1,163 +1,117 @@
1
  import os
2
- import arxiv
3
- import requests
4
  import streamlit as st
5
- from dotenv import load_dotenv
 
6
  from duckduckgo_search import DDGS
7
- import subprocess
8
- import re
9
 
10
- # Load environment
11
  load_dotenv()
12
- API_KEY = os.getenv("OPENROUTER_API_KEY")
13
-
14
- # ========== UTILITY FUNCTIONS ==========
15
- def sanitize_filename(title):
16
- return re.sub(r'[^\w\s-]', '', title).replace(" ", "_")[:50]
17
-
18
- def escape_latex(text):
19
- replacements = {
20
- '&': r'\&', '%': r'\%', '$': r'\$', '#': r'\#',
21
- '_': r'\_', '{': r'\{', '}': r'\}', '~': r'\textasciitilde{}',
22
- '^': r'\^{}', '\\': r'\textbackslash{}',
23
  }
24
- for original, replacement in replacements.items():
25
- text = text.replace(original, replacement)
26
- return text
27
-
28
- # ========== LLM INTERACTION ==========
29
- def call_llm(prompt):
30
- try:
31
- url = "https://openrouter.ai/api/v1/chat/completions"
32
- headers = {
33
- "Authorization": f"Bearer {API_KEY}",
34
- "Content-Type": "application/json"
35
- }
36
- data = {
37
- "model": "google/gemma-3-1b-it:free",
38
- "messages": [{"role": "user", "content": prompt}]
39
- }
40
- response = requests.post(url, headers=headers, json=data)
41
- response.raise_for_status()
42
- return response.json()['choices'][0]['message']['content']
43
- except Exception as e:
44
- return f"❌ LLM Error: {str(e)}"
45
-
46
- # ========== FETCH RESEARCH ==========
47
- def fetch_arxiv_papers(topic):
48
- results = arxiv.Search(query=topic, max_results=5, sort_by=arxiv.SortCriterion.SubmittedDate)
49
- return [{
50
- "title": result.title,
51
- "summary": result.summary,
52
- "url": result.pdf_url
53
- } for result in results.results()]
54
-
55
- def fetch_image_url(query):
56
- try:
57
- with DDGS() as ddgs:
58
- results = list(ddgs.images(query, max_results=1))
59
- if results:
60
- return results[0]['image']
61
- except:
62
- pass
63
- return None
64
-
65
- # ========== PDF EXPORT ==========
66
- def create_latex(title, content, image_url):
67
- title_safe = sanitize_filename(title)
68
- content = escape_latex(content)
69
- tex = f"""
70
- \\documentclass[12pt]{{article}}
71
- \\usepackage[margin=1in]{{geometry}}
72
- \\usepackage{{graphicx}}
73
- \\usepackage{{hyperref}}
74
- \\title{{\\textbf{{{escape_latex(title)}}}}}
75
- \\date{{\\today}}
76
- \\begin{{document}}
77
- \\maketitle
78
- """
79
-
80
- if image_url:
81
- img_data = requests.get(image_url).content
82
- with open("image.jpg", "wb") as img:
83
- img.write(img_data)
84
- tex += """
85
- \\begin{figure}[h]
86
- \\centering
87
- \\includegraphics[width=0.7\\textwidth]{image.jpg}
88
- \\caption{Auto-fetched Diagram}
89
- \\end{figure}
90
- """
91
-
92
- tex += f"{content}\n\\end{{document}}"
93
-
94
- tex_file = f"{title_safe}.tex"
95
- with open(tex_file, "w", encoding="utf-8") as f:
96
- f.write(tex)
97
-
98
- subprocess.run(["pdflatex", tex_file], stdout=subprocess.DEVNULL)
99
- return f"{title_safe}.pdf"
100
-
101
- # ========== STREAMLIT UI ==========
102
- st.set_page_config("AI Research Assistant", layout="wide")
103
- st.title("πŸ§ͺ AI-Powered Research Assistant")
104
-
105
- topic = st.text_input("πŸ” Enter your research topic:")
106
-
107
- if topic:
108
- with st.spinner("πŸ”Ž Fetching relevant arXiv papers..."):
109
- papers = fetch_arxiv_papers(topic)
110
- summaries = "\n\n".join([f"Title: {p['title']}\nSummary: {p['summary']}" for p in papers])
111
-
112
- st.subheader("πŸ“„ Recent arXiv Papers")
113
- for p in papers:
114
- st.markdown(f"**{p['title']}**\n[πŸ”— PDF Link]({p['url']})\n> {p['summary'][:300]}...")
115
-
116
- with st.spinner("🧠 Analyzing gaps and proposing ideas..."):
117
- gaps = call_llm(f"You're a top AI researcher. Read the summaries and find research gaps:\n\n{summaries}")
118
- idea = call_llm(f"Based on the gaps below, propose a novel research idea:\n\n{gaps}")
119
- paper_prompt = f"""
120
- You're an expert AI researcher and LaTeX academic writer.
121
-
122
- Write a **complete research paper** in well-formatted plain text (NOT just sections or instructions) titled: **"{topic}"**, based on this novel idea:
123
-
124
- "{idea}"
125
-
126
- Follow this structure **strictly** (include headings for each):
127
- 1. Abstract
128
- 2. Introduction
129
- 3. Related Work
130
- 4. Methodology
131
- 5. Experiments
132
- 6. Results & Discussion
133
- 7. Conclusion
134
- 8. References
135
-
136
- ⚠️ Do NOT ask the user to provide content. Write the full content yourself.
137
-
138
- Limit the total length to approximately 2000-2500 words to avoid truncation.
139
- Write in an academic tone and avoid repetition.
140
- Include citations in the format [Author, Year] without needing actual sources.
141
- Make sure it's export-ready.
142
- """
143
-
144
- paper = call_llm(paper_prompt)
145
-
146
-
147
- st.subheader("πŸ’‘ Novel Research Idea")
148
- st.markdown(idea)
149
-
150
- st.subheader("πŸ“ƒ Full Generated Paper")
151
- st.text_area("Academic Paper", paper, height=600)
152
-
153
- with st.spinner("πŸ–ΌοΈ Fetching diagram..."):
154
- image_url = fetch_image_url(topic)
155
- if image_url:
156
- st.image(image_url, caption="Auto-fetched relevant diagram")
157
-
158
- if st.button("πŸ“₯ Export to PDF"):
159
- with st.spinner("πŸ“„ Generating PDF..."):
160
- pdf_file = create_latex(topic, paper, image_url)
161
- with open(pdf_file, "rb") as f:
162
- st.download_button("Download Paper as PDF", f, file_name=pdf_file)
163
-
 
1
  import os
 
 
2
  import streamlit as st
3
+ import requests
4
+ import feedparser
5
  from duckduckgo_search import DDGS
6
+ from dotenv import load_dotenv
 
7
 
 
8
  load_dotenv()
9
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") # secure access
10
+
11
+ # --- LLM Wrapper ---
12
+ def call_llm(messages, model="deepseek/deepseek-chat-v3-0324:free", max_tokens=2048, temperature=0.7):
13
+ url = "https://openrouter.ai/api/v1/chat/completions"
14
+ headers = {
15
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
16
+ "Content-Type": "application/json",
17
+ "X-Title": "Autonomous Research Agent"
 
 
18
  }
19
+ data = {
20
+ "model": model,
21
+ "messages": messages,
22
+ "max_tokens": max_tokens,
23
+ "temperature": temperature
24
+ }
25
+ response = requests.post(url, headers=headers, json=data)
26
+ result = response.json()
27
+ if "choices" not in result:
28
+ raise RuntimeError(f"LLM returned invalid response: {result}")
29
+ return result["choices"][0]["message"]["content"]
30
+
31
+ # --- Research Source Functions ---
32
+ def get_arxiv_papers(query, max_results=3):
33
+ from urllib.parse import quote_plus
34
+ url = f"http://export.arxiv.org/api/query?search_query=all:{quote_plus(query)}&start=0&max_results={max_results}"
35
+ feed = feedparser.parse(url)
36
+ papers = []
37
+ for entry in feed.entries:
38
+ pdf = next((link.href for link in entry.links if link.type == "application/pdf"), "")
39
+ papers.append({"title": entry.title, "summary": entry.summary[:300], "url": pdf})
40
+ return papers
41
+
42
+ def get_semantic_scholar_papers(query, max_results=3):
43
+ url = "https://api.semanticscholar.org/graph/v1/paper/search"
44
+ params = {"query": query, "limit": max_results, "fields": "title,abstract,url"}
45
+ response = requests.get(url, params=params)
46
+ results = response.json().get("data", [])
47
+ return [{"title": p["title"], "summary": p.get("abstract", "N/A")[:300], "url": p.get("url", "")} for p in results]
48
+
49
+ def search_duckduckgo_snippets(query, max_results=3):
50
+ with DDGS() as ddgs:
51
+ return [
52
+ {"title": r["title"], "snippet": r["body"], "url": r["href"]}
53
+ for r in ddgs.text(query, max_results=max_results)
54
+ ]
55
+
56
+ def get_image_urls(query, max_images=1):
57
+ with DDGS() as ddgs:
58
+ return [img["image"] for img in ddgs.images(query, max_results=max_images)]
59
+
60
+ # --- Research Agent ---
61
+ def autonomous_research_agent(topic):
62
+ arxiv = get_arxiv_papers(topic)
63
+ scholar = get_semantic_scholar_papers(topic)
64
+ web = search_duckduckgo_snippets(topic)
65
+ images = get_image_urls(topic)
66
+
67
+ prompt = f"Topic: {topic}\n\n"
68
+
69
+ if images:
70
+ prompt += f"![Related Image]({images[0]})\n\n"
71
+
72
+ prompt += "## ArXiv:\n" + "\n".join(f"- [{p['title']}]({p['url']})\n> {p['summary']}..." for p in arxiv) + "\n\n"
73
+ prompt += "## Semantic Scholar:\n" + "\n".join(f"- [{p['title']}]({p['url']})\n> {p['summary']}..." for p in scholar) + "\n\n"
74
+ prompt += "## Web:\n" + "\n".join(f"- [{w['title']}]({w['url']})\n> {w['snippet']}" for w in web) + "\n\n"
75
+
76
+ prompt += (
77
+ "Now synthesize all this into:\n"
78
+ "1. Research gap\n"
79
+ "2. Proposed research direction\n"
80
+ "3. A full academic narrative (markdown format, formal tone)"
81
+ )
82
+
83
+ return call_llm([{"role": "user", "content": prompt}], max_tokens=3000)
84
+
85
+ # --- Streamlit UI ---
86
+ st.set_page_config("Autonomous Research Agent", layout="wide")
87
+ st.title("πŸ€– Autonomous AI Research Assistant")
88
+
89
+ if "chat_history" not in st.session_state:
90
+ st.session_state.chat_history = []
91
+
92
+ topic = st.text_input("Enter a research topic:")
93
+ if st.button("Run Agent"):
94
+ with st.spinner("Researching..."):
95
+ try:
96
+ output = autonomous_research_agent(topic)
97
+ st.session_state.chat_history.append({"role": "user", "content": topic})
98
+ st.session_state.chat_history.append({"role": "assistant", "content": output})
99
+ st.markdown(output)
100
+ except Exception as e:
101
+ st.error(f"Error: {e}")
102
+
103
+ # --- Follow-up Chat ---
104
+ st.divider()
105
+ st.subheader("πŸ’¬ Ask Follow-up Questions")
106
+ followup = st.text_input("Ask something based on the previous research:")
107
+ if st.button("Send"):
108
+ if followup:
109
+ chat = st.session_state.chat_history + [{"role": "user", "content": followup}]
110
+ with st.spinner("Thinking..."):
111
+ try:
112
+ response = call_llm(chat, max_tokens=1500)
113
+ st.session_state.chat_history.append({"role": "user", "content": followup})
114
+ st.session_state.chat_history.append({"role": "assistant", "content": response})
115
+ st.markdown(response)
116
+ except Exception as e:
117
+ st.error(f"Follow-up failed: {e}")