Nur Arifin Akbar commited on
Commit
9a4a0bb
Β·
0 Parent(s):

Initial commit: AI Literature Review System

Browse files

- Multi-agent review system with 3 specialized reviewers
- MarkItDown integration for PDF processing
- Semantic Scholar API integration for related papers
- OpenAI-compatible API support
- Gradio interface with progress tracking
- Sequential review processing

Files changed (6) hide show
  1. .env.example +13 -0
  2. .gitignore +46 -0
  3. README.md +0 -0
  4. agents.py +289 -0
  5. app.py +306 -0
  6. requirements.txt +5 -0
.env.example ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenAI-compatible API Configuration
2
+ OPENAI_API_KEY=your-api-key-here
3
+ OPENAI_BASE_URL=https://api.openai.com/v1
4
+ MODEL_NAME=gpt-4
5
+
6
+ # Alternative configurations:
7
+ # For Azure OpenAI:
8
+ # OPENAI_BASE_URL=https://your-resource.openai.azure.com/
9
+ # MODEL_NAME=your-deployment-name
10
+
11
+ # For custom endpoints (e.g., LocalAI, vLLM, etc.):
12
+ # OPENAI_BASE_URL=http://localhost:8000/v1
13
+ # MODEL_NAME=your-model-name
.gitignore ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ env/
8
+ venv/
9
+ ENV/
10
+ build/
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # Gradio
27
+ gradio_cached_examples/
28
+ flagged/
29
+
30
+ # Environment variables
31
+ .env
32
+ .env.local
33
+
34
+ # IDE
35
+ .vscode/
36
+ .idea/
37
+ *.swp
38
+ *.swo
39
+ *~
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Logs
46
+ *.log
README.md ADDED
Binary file (6.43 kB). View file
 
agents.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Multi-agent system for literature review using OpenAI-compatible API."""
2
+
3
+ import json
4
+ import re
5
+ import os
6
+ from typing import Any, Optional, Dict, Tuple
7
+ from openai import OpenAI
8
+
9
+
10
+ def extract_json_between_markers(llm_output: str) -> Optional[Dict[str, Any]]:
11
+ """Extracts JSON content from a string, typically an LLM output."""
12
+ json_pattern = r"```json(.*?)```"
13
+ matches = re.findall(json_pattern, llm_output, re.DOTALL)
14
+
15
+ if not matches:
16
+ json_pattern_fallback = r"\{[^{}]*\}"
17
+ matches = re.findall(json_pattern_fallback, llm_output, re.DOTALL)
18
+
19
+ for json_string in matches:
20
+ json_string = json_string.strip()
21
+ try:
22
+ parsed_json = json.loads(json_string)
23
+ return parsed_json
24
+ except json.JSONDecodeError:
25
+ try:
26
+ json_string_clean = "".join(
27
+ char for char in json_string if ord(char) >= 32 and ord(char) != 127
28
+ )
29
+ parsed_json = json.loads(json_string_clean)
30
+ return parsed_json
31
+ except json.JSONDecodeError:
32
+ continue
33
+
34
+ return None
35
+
36
+
37
+ def query_model(system_prompt: str, prompt: str, client: OpenAI, model: str) -> Optional[str]:
38
+ """Query the model with the given prompts using OpenAI-compatible API."""
39
+ try:
40
+ response = client.chat.completions.create(
41
+ model=model,
42
+ messages=[
43
+ {"role": "system", "content": system_prompt},
44
+ {"role": "user", "content": prompt}
45
+ ],
46
+ temperature=0.7,
47
+ max_tokens=4000
48
+ )
49
+ return response.choices[0].message.content
50
+ except Exception as e:
51
+ print(f"Error querying model: {e}")
52
+ return None
53
+
54
+
55
+ def get_score(
56
+ paper_content: str,
57
+ reviewer_type: Optional[str] = None,
58
+ attempts: int = 3,
59
+ client: OpenAI = None,
60
+ model: str = None,
61
+ ) -> Tuple[Optional[float], str, bool]:
62
+ """Evaluates a research paper using an LLM reviewer."""
63
+
64
+ last_exception_message = ""
65
+ for attempt in range(attempts):
66
+ try:
67
+ template_instructions = """
68
+ Respond in the following format:
69
+
70
+ THOUGHT:
71
+ <THOUGHT>
72
+
73
+ REVIEW JSON:
74
+ ```json
75
+ <JSON>
76
+ ```
77
+
78
+ In <THOUGHT>, first briefly discuss your intuitions and reasoning for the evaluation.
79
+ Detail your high-level arguments, necessary choices and desired outcomes of the review.
80
+
81
+ In <JSON>, provide the review in JSON format with the following fields:
82
+ - "Summary": A summary of the paper content and its contributions.
83
+ - "Strengths": A list of strengths of the paper.
84
+ - "Weaknesses": A list of weaknesses of the paper.
85
+ - "Originality": A rating from 1 to 4 (low, medium, high, very high).
86
+ - "Quality": A rating from 1 to 4 (low, medium, high, very high).
87
+ - "Clarity": A rating from 1 to 4 (low, medium, high, very high).
88
+ - "Significance": A rating from 1 to 4 (low, medium, high, very high).
89
+ - "Questions": A set of clarifying questions to be answered by the paper authors.
90
+ - "Limitations": A set of limitations and potential negative societal impacts.
91
+ - "Ethical Concerns": A boolean value indicating whether there are ethical concerns.
92
+ - "Soundness": A rating from 1 to 4 (poor, fair, good, excellent).
93
+ - "Presentation": A rating from 1 to 4 (poor, fair, good, excellent).
94
+ - "Contribution": A rating from 1 to 4 (poor, fair, good, excellent).
95
+ - "Overall": A rating from 1 to 10 (very strong reject to award quality).
96
+ - "Confidence": A rating from 1 to 5 (low, medium, high, very high, absolute).
97
+ - "Decision": A decision that has to be one of: Accept, Reject.
98
+ """
99
+
100
+ neurips_form = """
101
+ ## Review Guidelines
102
+
103
+ Evaluate the paper across these dimensions:
104
+
105
+ 1. **Originality**: Are the ideas novel? Is related work cited?
106
+ 2. **Quality**: Is the work technically sound? Are claims well supported?
107
+ 3. **Clarity**: Is the paper well-written and organized?
108
+ 4. **Significance**: Are the results important? Will others build on this work?
109
+ 5. **Soundness**: Rate the technical quality (1-4: poor, fair, good, excellent)
110
+ 6. **Presentation**: Rate the writing quality (1-4: poor, fair, good, excellent)
111
+ 7. **Contribution**: Rate the overall contribution (1-4: poor, fair, good, excellent)
112
+ 8. **Overall Score**: Rate 1-10 where:
113
+ - 1-3: Reject
114
+ - 4-6: Borderline
115
+ - 7-8: Accept
116
+ - 9-10: Strong Accept
117
+
118
+ """ + template_instructions
119
+
120
+ if reviewer_type is None:
121
+ reviewer_type = ""
122
+
123
+ sys_prompt = (
124
+ f"You are an AI researcher reviewing an academic paper. "
125
+ f"Be critical and thorough in your assessment. {reviewer_type}\n"
126
+ ) + neurips_form
127
+
128
+ prompt = f"Review the following paper:\n\n{paper_content}\n\n"
129
+
130
+ review_output = query_model(
131
+ system_prompt=sys_prompt,
132
+ prompt=prompt,
133
+ client=client,
134
+ model=model,
135
+ )
136
+
137
+ if review_output is None:
138
+ raise ValueError("LLM query returned None.")
139
+
140
+ review_json = extract_json_between_markers(review_output)
141
+
142
+ if review_json is None:
143
+ raise ValueError("Could not extract JSON review from LLM output.")
144
+
145
+ required_keys = [
146
+ "Overall", "Soundness", "Confidence", "Contribution",
147
+ "Presentation", "Clarity", "Originality", "Quality", "Significance",
148
+ ]
149
+
150
+ for key in required_keys:
151
+ if key not in review_json:
152
+ raise KeyError(f"Missing key '{key}' in review JSON.")
153
+
154
+ # Calculate weighted score
155
+ overall = int(review_json["Overall"]) / 10.0
156
+ soundness = int(review_json["Soundness"]) / 4.0
157
+ confidence = int(review_json["Confidence"]) / 5.0
158
+ contribution = int(review_json["Contribution"]) / 4.0
159
+ presentation = int(review_json["Presentation"]) / 4.0
160
+ clarity = int(review_json["Clarity"]) / 4.0
161
+ originality = int(review_json["Originality"]) / 4.0
162
+ quality = int(review_json["Quality"]) / 4.0
163
+ significance = int(review_json["Significance"]) / 4.0
164
+
165
+ weights = {
166
+ "clarity": 0.1,
167
+ "quality": 0.1,
168
+ "overall": 1.0,
169
+ "soundness": 0.1,
170
+ "confidence": 0.1,
171
+ "originality": 0.1,
172
+ "significance": 0.1,
173
+ "contribution": 0.4,
174
+ "presentation": 0.2,
175
+ }
176
+
177
+ max_score = sum(weights.values())
178
+
179
+ performance = (
180
+ weights["soundness"] * soundness +
181
+ weights["presentation"] * presentation +
182
+ weights["confidence"] * confidence +
183
+ weights["contribution"] * contribution +
184
+ weights["overall"] * overall +
185
+ weights["originality"] * originality +
186
+ weights["significance"] * significance +
187
+ weights["clarity"] * clarity +
188
+ weights["quality"] * quality
189
+ ) / max_score * 10.0
190
+
191
+ return (
192
+ performance,
193
+ f"Performance Score: {performance:.2f}/10\n\n{review_output}",
194
+ True,
195
+ )
196
+
197
+ except Exception as e:
198
+ print(f"Error in get_score (attempt {attempt + 1}/{attempts}): {e}")
199
+ last_exception_message = str(e)
200
+
201
+ return (
202
+ None,
203
+ f"Failed to get score after {attempts} attempts. Last error: {last_exception_message}",
204
+ False,
205
+ )
206
+
207
+
208
+ class ReviewerAgent:
209
+ """Agent that simulates a single reviewer with specific persona."""
210
+
211
+ def __init__(self, client: OpenAI, model: str, persona: str, name: str):
212
+ self.client = client
213
+ self.model = model
214
+ self.persona = persona
215
+ self.name = name
216
+
217
+ def review_paper(self, paper_content: str) -> Dict[str, Any]:
218
+ """Generate review for the paper."""
219
+ score, review_text, success = get_score(
220
+ paper_content=paper_content,
221
+ reviewer_type=self.persona,
222
+ client=self.client,
223
+ model=self.model,
224
+ )
225
+
226
+ return {
227
+ "reviewer": self.name,
228
+ "score": score,
229
+ "review": review_text,
230
+ "success": success
231
+ }
232
+
233
+
234
+ class MultiReviewerSystem:
235
+ """System that coordinates multiple reviewer agents."""
236
+
237
+ def __init__(self, api_key: str, base_url: str, model: str):
238
+ self.client = OpenAI(api_key=api_key, base_url=base_url)
239
+ self.model = model
240
+
241
+ self.reviewers = [
242
+ ReviewerAgent(
243
+ client=self.client,
244
+ model=self.model,
245
+ persona="You focus on experimental rigor and expect well-designed experiments with clear insights.",
246
+ name="Reviewer 1: Experimentalist"
247
+ ),
248
+ ReviewerAgent(
249
+ client=self.client,
250
+ model=self.model,
251
+ persona="You look for impactful ideas that would advance the field significantly.",
252
+ name="Reviewer 2: Impactist"
253
+ ),
254
+ ReviewerAgent(
255
+ client=self.client,
256
+ model=self.model,
257
+ persona="You seek novel ideas that have not been proposed before and creative approaches.",
258
+ name="Reviewer 3: Novelty Seeker"
259
+ )
260
+ ]
261
+
262
+ def review_paper_sequential(self, paper_content: str, progress_callback=None) -> Dict[str, Any]:
263
+ """Generate reviews from multiple reviewers sequentially."""
264
+ reviews = []
265
+ total_score = 0
266
+ successful_reviews = 0
267
+
268
+ for i, reviewer in enumerate(self.reviewers):
269
+ if progress_callback:
270
+ progress_callback(i / len(self.reviewers), f"Reviewing with {reviewer.name}...")
271
+
272
+ review_result = reviewer.review_paper(paper_content)
273
+ reviews.append(review_result)
274
+
275
+ if review_result["success"] and review_result["score"] is not None:
276
+ total_score += review_result["score"]
277
+ successful_reviews += 1
278
+
279
+ avg_score = total_score / successful_reviews if successful_reviews > 0 else 0
280
+
281
+ if progress_callback:
282
+ progress_callback(1.0, "Review complete!")
283
+
284
+ return {
285
+ "reviews": reviews,
286
+ "average_score": avg_score,
287
+ "total_reviewers": len(self.reviewers),
288
+ "successful_reviews": successful_reviews
289
+ }
app.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio app for AI-powered literature review system with Semantic Scholar integration."""
2
+
3
+ import gradio as gr
4
+ import os
5
+ from typing import Optional, List, Dict
6
+ from markitdown import MarkItDown
7
+ from agents import MultiReviewerSystem
8
+ import requests
9
+ import time
10
+
11
+
12
+ def extract_text_from_pdf(pdf_file) -> str:
13
+ """Extract text content from a PDF file using markitdown."""
14
+ try:
15
+ if pdf_file is None:
16
+ return ""
17
+
18
+ md = MarkItDown()
19
+ result = md.convert(pdf_file.name)
20
+ return result.text_content
21
+
22
+ except Exception as e:
23
+ return f"Error extracting text from PDF: {str(e)}"
24
+
25
+
26
+ def search_semantic_scholar(query: str, limit: int = 5) -> List[Dict]:
27
+ """Search for related papers on Semantic Scholar."""
28
+ try:
29
+ url = "https://api.semanticscholar.org/graph/v1/paper/search"
30
+ params = {
31
+ "query": query,
32
+ "limit": limit,
33
+ "fields": "title,authors,year,abstract,citationCount,url,openAccessPdf"
34
+ }
35
+
36
+ response = requests.get(url, params=params)
37
+ response.raise_for_status()
38
+
39
+ data = response.json()
40
+ return data.get("data", [])
41
+
42
+ except Exception as e:
43
+ print(f"Error searching Semantic Scholar: {e}")
44
+ return []
45
+
46
+
47
+ def format_semantic_scholar_results(papers: List[Dict]) -> str:
48
+ """Format Semantic Scholar results for display."""
49
+ if not papers:
50
+ return "No related papers found."
51
+
52
+ formatted = "## πŸ“š Related Papers from Semantic Scholar\n\n"
53
+
54
+ for i, paper in enumerate(papers, 1):
55
+ title = paper.get("title", "N/A")
56
+ authors = ", ".join([a.get("name", "") for a in paper.get("authors", [])])
57
+ year = paper.get("year", "N/A")
58
+ citations = paper.get("citationCount", 0)
59
+ abstract = paper.get("abstract", "No abstract available")
60
+ url = paper.get("url", "")
61
+ pdf_url = paper.get("openAccessPdf", {})
62
+
63
+ formatted += f"### {i}. {title}\n\n"
64
+ formatted += f"**Authors**: {authors}\n\n"
65
+ formatted += f"**Year**: {year} | **Citations**: {citations}\n\n"
66
+ formatted += f"**Abstract**: {abstract[:300]}{'...' if len(abstract) > 300 else ''}\n\n"
67
+
68
+ if url:
69
+ formatted += f"[View on Semantic Scholar]({url})"
70
+
71
+ if pdf_url and pdf_url.get("url"):
72
+ formatted += f" | [Download PDF]({pdf_url['url']})"
73
+
74
+ formatted += "\n\n---\n\n"
75
+
76
+ return formatted
77
+
78
+
79
+ def extract_paper_title_from_text(text: str) -> str:
80
+ """Extract paper title from the beginning of the text."""
81
+ lines = text.split('\n')
82
+ for line in lines[:20]: # Check first 20 lines
83
+ line = line.strip()
84
+ if len(line) > 20 and len(line) < 200: # Reasonable title length
85
+ return line
86
+ return "Research Paper"
87
+
88
+
89
+ def review_paper(
90
+ pdf_file,
91
+ api_key: str,
92
+ base_url: str,
93
+ model_name: str,
94
+ search_related: bool,
95
+ progress=gr.Progress()
96
+ ) -> tuple[str, str, str, str, str]:
97
+ """Main function to process PDF and generate reviews."""
98
+
99
+ if pdf_file is None:
100
+ return "Please upload a PDF file.", "", "", "", ""
101
+
102
+ # Get API credentials from environment or inputs
103
+ final_api_key = api_key if api_key else os.getenv("OPENAI_API_KEY", "")
104
+ final_base_url = base_url if base_url else os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
105
+ final_model = model_name if model_name else os.getenv("MODEL_NAME", "gpt-3.5-turbo")
106
+
107
+ if not final_api_key or final_api_key.strip() == "":
108
+ return "Please provide an API key or set OPENAI_API_KEY environment variable.", "", "", "", ""
109
+
110
+ # Extract text from PDF
111
+ progress(0.1, desc="Extracting text from PDF...")
112
+ paper_text = extract_text_from_pdf(pdf_file)
113
+
114
+ if paper_text.startswith("Error"):
115
+ return paper_text, "", "", "", ""
116
+
117
+ if len(paper_text.strip()) == 0:
118
+ return "Could not extract text from PDF. The file might be empty or image-based.", "", "", "", ""
119
+
120
+ # Search for related papers if requested
121
+ related_papers_md = ""
122
+ if search_related:
123
+ progress(0.2, desc="Searching for related papers...")
124
+ paper_title = extract_paper_title_from_text(paper_text)
125
+ related_papers = search_semantic_scholar(paper_title, limit=5)
126
+ related_papers_md = format_semantic_scholar_results(related_papers)
127
+ time.sleep(1) # Rate limiting
128
+
129
+ # Initialize multi-reviewer system
130
+ progress(0.3, desc="Initializing reviewers...")
131
+
132
+ try:
133
+ reviewer_system = MultiReviewerSystem(
134
+ api_key=final_api_key,
135
+ base_url=final_base_url,
136
+ model=final_model
137
+ )
138
+
139
+ # Generate reviews
140
+ def progress_callback(value, desc):
141
+ progress(0.3 + (value * 0.7), desc=desc)
142
+
143
+ result = reviewer_system.review_paper_sequential(
144
+ paper_text,
145
+ progress_callback=progress_callback
146
+ )
147
+
148
+ # Format summary
149
+ summary = f"""
150
+ ## Review Summary
151
+
152
+ **Average Score**: {result['average_score']:.2f}/10
153
+ **Successful Reviews**: {result['successful_reviews']}/{result['total_reviewers']}
154
+
155
+ ---
156
+ """
157
+
158
+ # Extract individual reviews
159
+ review_1 = ""
160
+ review_2 = ""
161
+ review_3 = ""
162
+
163
+ for i, review_data in enumerate(result['reviews']):
164
+ score_text = f"{review_data['score']:.2f}/10" if review_data['score'] else 'N/A'
165
+ review_text = f"""
166
+ ### {review_data['reviewer']}
167
+
168
+ **Score**: {score_text}
169
+
170
+ {review_data['review']}
171
+
172
+ ---
173
+ """
174
+ if i == 0:
175
+ review_1 = review_text
176
+ elif i == 1:
177
+ review_2 = review_text
178
+ elif i == 2:
179
+ review_3 = review_text
180
+
181
+ return summary, review_1, review_2, review_3, related_papers_md
182
+
183
+ except Exception as e:
184
+ error_msg = f"Error during review process: {str(e)}"
185
+ return error_msg, "", "", "", related_papers_md
186
+
187
+
188
+ # Create Gradio interface
189
+ with gr.Blocks(title="AI Literature Review System", theme=gr.themes.Soft()) as demo:
190
+ gr.Markdown("""
191
+ # πŸ“š AI-Powered Literature Review System
192
+
193
+ Upload a research paper (PDF) and get comprehensive reviews from multiple AI agents with different perspectives.
194
+
195
+ ## Features:
196
+ - **Multi-Agent Review**: Three specialized reviewers evaluate your paper sequentially
197
+ - **Comprehensive Analysis**: Originality, quality, clarity, significance, and more
198
+ - **Detailed Feedback**: Strengths, weaknesses, questions, and suggestions
199
+ - **Scoring System**: Based on top-tier conference standards (NeurIPS-style)
200
+ - **Semantic Scholar Integration**: Find related papers for comparison
201
+ """)
202
+
203
+ with gr.Row():
204
+ with gr.Column(scale=1):
205
+ gr.Markdown("### πŸ“€ Upload & Configure")
206
+
207
+ with gr.Accordion("API Configuration", open=False):
208
+ api_key_input = gr.Textbox(
209
+ label="API Key",
210
+ type="password",
211
+ placeholder="Leave empty to use OPENAI_API_KEY env var",
212
+ info="Your OpenAI-compatible API key"
213
+ )
214
+
215
+ base_url_input = gr.Textbox(
216
+ label="Base URL",
217
+ placeholder="Leave empty to use OPENAI_BASE_URL env var or default",
218
+ info="API base URL (e.g., https://api.openai.com/v1)"
219
+ )
220
+
221
+ model_input = gr.Textbox(
222
+ label="Model Name",
223
+ placeholder="Leave empty to use MODEL_NAME env var or default",
224
+ info="Model identifier (e.g., gpt-4, gpt-3.5-turbo)"
225
+ )
226
+
227
+ pdf_input = gr.File(
228
+ label="Upload Research Paper (PDF)",
229
+ file_types=[".pdf"],
230
+ type="filepath"
231
+ )
232
+
233
+ search_related_checkbox = gr.Checkbox(
234
+ label="Search for related papers on Semantic Scholar",
235
+ value=True,
236
+ info="Find similar papers for comparison"
237
+ )
238
+
239
+ submit_btn = gr.Button("πŸ” Review Paper", variant="primary", size="lg")
240
+
241
+ gr.Markdown("""
242
+ ### πŸ‘₯ Reviewers (Sequential):
243
+ 1. **Experimentalist**: Methodology and results
244
+ 2. **Impactist**: Impact and significance
245
+ 3. **Novelty Seeker**: Originality and innovation
246
+
247
+ ### πŸ”§ Setup:
248
+ Set environment variables in `.env`:
249
+ ```bash
250
+ OPENAI_API_KEY=your-key-here
251
+ OPENAI_BASE_URL=https://api.openai.com/v1
252
+ MODEL_NAME=gpt-4
253
+ ```
254
+ """)
255
+
256
+ with gr.Column(scale=2):
257
+ gr.Markdown("### πŸ“Š Review Results")
258
+
259
+ summary_output = gr.Markdown(label="Summary")
260
+
261
+ with gr.Tabs():
262
+ with gr.Tab("Reviewer 1: Experimentalist"):
263
+ review_1_output = gr.Markdown()
264
+
265
+ with gr.Tab("Reviewer 2: Impactist"):
266
+ review_2_output = gr.Markdown()
267
+
268
+ with gr.Tab("Reviewer 3: Novelty Seeker"):
269
+ review_3_output = gr.Markdown()
270
+
271
+ with gr.Tab("Related Papers"):
272
+ related_papers_output = gr.Markdown()
273
+
274
+ # Connect the button to the review function
275
+ submit_btn.click(
276
+ fn=review_paper,
277
+ inputs=[pdf_input, api_key_input, base_url_input, model_input, search_related_checkbox],
278
+ outputs=[summary_output, review_1_output, review_2_output, review_3_output, related_papers_output]
279
+ )
280
+
281
+ gr.Markdown("""
282
+ ---
283
+ ### πŸ“– How to Use:
284
+ 1. Configure your API settings (or use environment variables)
285
+ 2. Upload your research paper in PDF format
286
+ 3. Optionally enable Semantic Scholar search for related papers
287
+ 4. Click "Review Paper" and wait for the sequential multi-agent analysis (2-5 minutes)
288
+ 5. Review the detailed feedback from all three reviewers
289
+
290
+ ### πŸ“Š Score Interpretation:
291
+ - **9-10**: Award Quality / Strong Accept
292
+ - **7-8**: Accept
293
+ - **5-6**: Borderline
294
+ - **3-4**: Borderline Reject
295
+ - **1-2**: Reject
296
+
297
+ ### ⚠️ Notes:
298
+ - Reviews are generated **sequentially** (one at a time) for better quality
299
+ - Processing time depends on paper length and API response time
300
+ - Ensure your PDF contains extractable text (not scanned images)
301
+ - Semantic Scholar API is rate-limited; use moderately
302
+ """)
303
+
304
+
305
+ if __name__ == "__main__":
306
+ demo.launch(share=False)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ markitdown>=0.0.1a2
3
+ openai>=1.0.0
4
+ requests>=2.31.0
5
+ python-dotenv>=1.0.0