MusaR commited on
Commit
a1a48d8
Β·
verified Β·
1 Parent(s): 042fd4f

Update research_agent/agent.py

Browse files
Files changed (1) hide show
  1. research_agent/agent.py +702 -131
research_agent/agent.py CHANGED
@@ -1,152 +1,723 @@
1
  import os
 
 
 
 
 
2
  import json
3
- from pydantic import BaseModel, Field
4
- from IPython.display import display, Markdown
5
-
6
- # Local module imports
7
- from .config import AgentConfig
8
- from . import prompts
9
- from .llm_utils import run_gemini_json_completion, run_gemini_text_completion
10
- from .search import gather_research
11
- from .rag_pipeline import RAGPipeline
12
-
13
- # For running async code in notebook
14
- import nest_asyncio
15
- nest_asyncio.apply()
16
-
17
- class Section(BaseModel):
18
- title: str = Field(description="The title of the report section.")
19
- description: str = Field(description="A detailed description of what the section will cover, including key sub-topics.")
20
-
21
- def run_verification_step(writer_model, section_text: str, research_context: str):
22
- """A new step to verify claims and check for hallucinations."""
23
- verification_prompt = prompts.verification_prompt_template.format(
24
- section_text=section_text,
25
- research_context=research_context
26
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- verification_result = run_gemini_text_completion(writer_model, verification_prompt, 0.0)
29
-
30
- if "OK" in verification_result.upper():
31
- return section_text
32
- else:
33
- return f"{section_text}\n\n---\n*Self-Correction Note: An issue was found during verification. The model suggested the following correction: {verification_result}*"
34
-
35
- def get_clarifying_questions(planner_model, initial_topic: str):
36
- """Generates clarifying questions for the user."""
37
- prompt = prompts.clarification_prompt_template.format(initial_topic=initial_topic)
38
- questions = run_gemini_text_completion(planner_model, prompt, 0.5)
39
- return questions
40
-
41
- def research_and_plan(config: AgentConfig, planner_model, tavily_client, initial_topic: str, user_answers: str):
42
- """Constructs the research brief and generates the report outline."""
43
- print("\n--- Step 1: Constructing Detailed Research Brief ---")
44
- brief_constructor_prompt = prompts.brief_constructor_prompt_template.format(
45
- initial_topic=initial_topic,
46
- user_answers=user_answers
47
- )
48
- detailed_topic = run_gemini_text_completion(planner_model, brief_constructor_prompt, config.PLANNER_TEMPERATURE).strip()
49
 
50
- if detailed_topic.startswith("[Error:"):
51
- raise ValueError(f"Could not construct a research brief from the provided topic. The LLM returned an error: {detailed_topic}")
52
 
53
- print(f"\n--- Step 2: Performing Broad Initial Research for Outline ---")
54
- initial_research = gather_research(tavily_client, [detailed_topic], config.INITIAL_SEARCH_RESULTS)
55
- planning_context = "\n\n".join(item['content'] for item in initial_research)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- planner_prompt = prompts.planner_prompt.format(topic=detailed_topic, context=planning_context[:20000])
58
- plan_response = run_gemini_json_completion(planner_model, planner_prompt, config.PLANNER_TEMPERATURE)
 
59
 
60
- try:
61
- initial_sections = [Section(**s) for s in plan_response.get("sections", [])]
62
- except Exception as e:
63
- raise ValueError(f"Could not create a valid report plan. Error: {e}")
64
- if not initial_sections:
65
- raise ValueError("Planner returned no sections.")
66
 
67
- print("\n--- Step 3: Expanding Outline for Deep Research ---")
68
- expanded_sections = []
69
- for section in initial_sections:
70
- expansion_prompt = prompts.expansion_prompt_template.format(section_title=section.title, section_description=section.description)
71
- sub_topics_text = run_gemini_text_completion(planner_model, expansion_prompt, 0.6)
72
- section.description += "\n\nKey areas to investigate:\n" + sub_topics_text
73
- expanded_sections.append(section)
74
 
75
- return {"detailed_topic": detailed_topic, "sections": expanded_sections}
 
 
 
76
 
77
- def write_report_stream(config: AgentConfig, writer_model, tavily_client, embedding_model, reranker, plan: dict):
78
- """Writes the report section by section, yielding progress updates."""
79
-
80
- detailed_topic = plan["detailed_topic"]
81
- sections = plan["sections"]
82
 
83
- yield f"### Starting Report Generation for: {detailed_topic}\n\n---\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- report_state = {
86
- "full_report_text": f"# Deep Research Report: {detailed_topic}\n\n",
87
- "all_source_urls": set(),
88
- "completed_section_titles": []
89
- }
90
- rag_pipeline = RAGPipeline(embedding_model, reranker)
91
 
92
- for i, section in enumerate(sections):
93
- yield f"### Processing Section {i+1}/{len(sections)}: {section.title}...\n\n"
94
-
95
- previous_sections_context = "\n".join(f"- {title}" for title in report_state["completed_section_titles"])
96
- if previous_sections_context:
97
- previous_sections_context = "The following sections have already been written:\n" + previous_sections_context
98
- else:
99
- previous_sections_context = "This is the first section."
100
-
101
- section_queries = [f"{detailed_topic} - {section.title}"] + section.description.split('\n')[-3:]
102
- section_queries = [q.strip() for q in section_queries if q.strip()]
103
- section_queries = [q[:400] for q in section_queries]
104
-
105
- yield f"-> Searching the web for: `{'`, `'.join(section_queries)}`\n"
106
- section_research = gather_research(tavily_client, section_queries, config.DEEP_DIVE_SEARCH_RESULTS)
107
 
108
- if not section_research:
109
- section_content = f"## {section.title}\n\nNo research material could be gathered for this section.\n\n"
110
- report_state["full_report_text"] += section_content
111
- continue
112
 
113
- yield f"-> Found {len(section_research)} sources. Indexing for RAG...\n"
114
- rag_pipeline.index_research(section_research)
115
- top_chunks_with_meta = rag_pipeline.retrieve_and_rerank(section.description, top_k=config.CHUNKS_TO_USE_FOR_WRITING)
116
 
117
- context_for_llm = ""
118
- cited_sources_for_section = {}
119
- citation_counter = 1
120
- for item in top_chunks_with_meta:
121
- source_url = item['source']
122
- report_state["all_source_urls"].add(source_url)
123
- if source_url not in cited_sources_for_section:
124
- cited_sources_for_section[source_url] = citation_counter
125
- citation_counter += 1
126
- citation_num = cited_sources_for_section[source_url]
127
- context_for_llm += f"Source [{citation_num}]: {item['content']}\n\n"
128
 
129
- bibliography = "\n".join(f"[{num}] {url}" for url, num in cited_sources_for_section.items())
130
-
131
- yield f"-> Synthesizing and writing section content...\n"
132
- yield f"--> Calling LLM for section '{section.title}'. This may take a moment...\n"
133
- writer_prompt = prompts.writer_prompt_template.format(
134
- writer_system_instruction=prompts.writer_system_instruction,
135
- previous_sections_context=previous_sections_context,
136
- section_title=section.title,
137
- context_for_llm=context_for_llm
138
- )
139
-
140
- draft_content = run_gemini_text_completion(writer_model, writer_prompt, config.WRITER_TEMPERATURE)
141
 
142
- yield "-> Fact-checking and verifying section...\n\n---\n"
143
- final_content = run_verification_step(writer_model, draft_content, context_for_llm)
144
- final_content_with_sources = f"## {section.title}\n\n{final_content}\n\n**Sources Used in this Section**\n{bibliography}\n\n"
 
 
 
 
145
 
146
- report_state["full_report_text"] += final_content_with_sources
147
- report_state["completed_section_titles"].append(section.title)
 
 
 
 
 
 
 
 
 
148
 
149
- final_bibliography = "\n".join(f"- {url}" for url in sorted(list(report_state["all_source_urls"])))
150
- report_state["full_report_text"] += f"## Master Bibliography\n\n{final_bibliography}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- yield report_state["full_report_text"]
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import gradio as gr
3
+ import google.generativeai as genai
4
+ from tavily import TavilyClient
5
+ from sentence_transformers import SentenceTransformer, CrossEncoder
6
+ from datetime import datetime
7
  import json
8
+ import time
9
+
10
+ from research_agent.config import AgentConfig
11
+ from research_agent.agent import get_clarifying_questions, research_and_plan, write_report_stream
12
+
13
+ google_key = os.getenv("GOOGLE_API_KEY")
14
+ tavily_key = os.getenv("TAVILY_API_KEY")
15
+
16
+ if not google_key or not tavily_key:
17
+ raise ValueError("API keys not found.")
18
+
19
+ # Professional CSS with dark theme
20
+ CSS = """
21
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono&display=swap');
22
+
23
+ * {
24
+ transition: all 0.3s ease;
25
+ }
26
+
27
+ body, .gradio-container {
28
+ font-family: 'Inter', -apple-system, system-ui, sans-serif !important;
29
+ background: #0a0a0a !important;
30
+ color: #e4e4e7 !important;
31
+ }
32
+
33
+ .gradio-container {
34
+ max-width: 1200px !important;
35
+ margin: 0 auto !important;
36
+ }
37
+
38
+ /* Header */
39
+ .header {
40
+ background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
41
+ padding: 3rem 2rem;
42
+ border-radius: 24px;
43
+ margin-bottom: 2rem;
44
+ text-align: center;
45
+ box-shadow: 0 20px 40px rgba(0,0,0,0.5);
46
+ }
47
+
48
+ .header h1 {
49
+ font-size: 3.5rem;
50
+ font-weight: 800;
51
+ background: linear-gradient(135deg, #818cf8 0%, #c084fc 50%, #f472b6 100%);
52
+ -webkit-background-clip: text;
53
+ -webkit-text-fill-color: transparent;
54
+ margin: 0;
55
+ letter-spacing: -1px;
56
+ }
57
+
58
+ .header p {
59
+ color: #a5b4fc;
60
+ font-size: 1.25rem;
61
+ margin-top: 0.5rem;
62
+ font-weight: 300;
63
+ }
64
+
65
+ /* Status Bar */
66
+ .status-bar {
67
+ background: #18181b;
68
+ border: 1px solid #27272a;
69
+ border-radius: 16px;
70
+ padding: 1rem 1.5rem;
71
+ margin-bottom: 2rem;
72
+ display: flex;
73
+ justify-content: space-between;
74
+ align-items: center;
75
+ }
76
+
77
+ .status-indicator {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 0.75rem;
81
+ }
82
+
83
+ .pulse-dot {
84
+ width: 10px;
85
+ height: 10px;
86
+ background: #22c55e;
87
+ border-radius: 50%;
88
+ box-shadow: 0 0 0 0 rgba(34, 197, 94, 1);
89
+ animation: pulse-animation 2s infinite;
90
+ }
91
+
92
+ @keyframes pulse-animation {
93
+ 0% {
94
+ box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
95
+ }
96
+ 70% {
97
+ box-shadow: 0 0 0 10px rgba(34, 197, 94, 0);
98
+ }
99
+ 100% {
100
+ box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
101
+ }
102
+ }
103
+
104
+ /* Chat Interface */
105
+ #chatbot {
106
+ background: #18181b !important;
107
+ border: 1px solid #27272a !important;
108
+ border-radius: 20px !important;
109
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5) !important;
110
+ }
111
+
112
+ #chatbot .message-wrap {
113
+ padding: 0 !important;
114
+ }
115
+
116
+ #chatbot .message {
117
+ padding: 1.5rem !important;
118
+ margin: 0.5rem !important;
119
+ border-radius: 16px !important;
120
+ font-size: 1.05rem !important;
121
+ line-height: 1.7 !important;
122
+ }
123
+
124
+ #chatbot .user {
125
+ background: linear-gradient(135deg, #4c1d95 0%, #5b21b6 100%) !important;
126
+ color: #f3f4f6 !important;
127
+ margin-left: 20% !important;
128
+ box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3) !important;
129
+ }
130
+
131
+ #chatbot .bot {
132
+ background: #27272a !important;
133
+ color: #e4e4e7 !important;
134
+ margin-right: 20% !important;
135
+ border: 1px solid #3f3f46 !important;
136
+ }
137
+
138
+ #chatbot .bot h1, #chatbot .bot h2, #chatbot .bot h3 {
139
+ color: #a78bfa !important;
140
+ margin-top: 1.5rem !important;
141
+ margin-bottom: 1rem !important;
142
+ }
143
+
144
+ #chatbot .bot h1 { font-size: 2rem !important; }
145
+ #chatbot .bot h2 { font-size: 1.5rem !important; }
146
+ #chatbot .bot h3 { font-size: 1.25rem !important; }
147
+
148
+ #chatbot .bot strong {
149
+ color: #c4b5fd !important;
150
+ }
151
+
152
+ #chatbot .bot code {
153
+ background: #374151 !important;
154
+ color: #fbbf24 !important;
155
+ padding: 2px 6px !important;
156
+ border-radius: 4px !important;
157
+ font-family: 'JetBrains Mono', monospace !important;
158
+ }
159
+
160
+ #chatbot .bot a {
161
+ color: #60a5fa !important;
162
+ text-decoration: none !important;
163
+ border-bottom: 1px solid transparent !important;
164
+ transition: border-color 0.2s !important;
165
+ }
166
+
167
+ #chatbot .bot a:hover {
168
+ border-bottom-color: #60a5fa !important;
169
+ }
170
+
171
+ /* Progress Messages */
172
+ .progress-message {
173
+ background: #1e1b4b;
174
+ border-left: 4px solid #6366f1;
175
+ padding: 1rem 1.5rem;
176
+ margin: 1rem 0;
177
+ border-radius: 0 8px 8px 0;
178
+ }
179
+
180
+ /* Input Area */
181
+ .input-group {
182
+ background: #18181b;
183
+ border: 1px solid #27272a;
184
+ border-radius: 16px;
185
+ padding: 1.5rem;
186
+ margin-top: 2rem;
187
+ }
188
+
189
+ #user-input textarea {
190
+ background: #27272a !important;
191
+ border: 2px solid #3f3f46 !important;
192
+ color: #f3f4f6 !important;
193
+ border-radius: 12px !important;
194
+ padding: 1rem !important;
195
+ font-size: 1.05rem !important;
196
+ min-height: 80px !important;
197
+ }
198
+
199
+ #user-input textarea:focus {
200
+ border-color: #6366f1 !important;
201
+ outline: none !important;
202
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1) !important;
203
+ }
204
+
205
+ /* Buttons */
206
+ .submit-btn {
207
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
208
+ color: white !important;
209
+ font-weight: 600 !important;
210
+ font-size: 1.1rem !important;
211
+ padding: 0.875rem 2rem !important;
212
+ border: none !important;
213
+ border-radius: 12px !important;
214
+ cursor: pointer !important;
215
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4) !important;
216
+ }
217
+
218
+ .submit-btn:hover {
219
+ transform: translateY(-2px) !important;
220
+ box-shadow: 0 6px 20px rgba(99, 102, 241, 0.5) !important;
221
+ }
222
+
223
+ .submit-btn:active {
224
+ transform: translateY(0) !important;
225
+ }
226
+
227
+ /* Examples */
228
+ .examples-container {
229
+ background: #18181b;
230
+ border: 1px solid #27272a;
231
+ border-radius: 16px;
232
+ padding: 1.5rem;
233
+ margin-top: 2rem;
234
+ }
235
+
236
+ .examples-container h3 {
237
+ color: #a78bfa;
238
+ margin-bottom: 1rem;
239
+ font-size: 1.25rem;
240
+ font-weight: 600;
241
+ }
242
+
243
+ .examples-grid {
244
+ display: grid;
245
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
246
+ gap: 1rem;
247
+ }
248
+
249
+ .example-card {
250
+ background: #27272a;
251
+ border: 1px solid #3f3f46;
252
+ padding: 1rem;
253
+ border-radius: 12px;
254
+ cursor: pointer;
255
+ transition: all 0.2s;
256
+ }
257
+
258
+ .example-card:hover {
259
+ background: #374151;
260
+ border-color: #6366f1;
261
+ transform: translateY(-2px);
262
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
263
+ }
264
+
265
+ /* Loading Animation */
266
+ .loading-wave {
267
+ display: inline-flex;
268
+ gap: 4px;
269
+ }
270
+
271
+ .loading-wave span {
272
+ width: 4px;
273
+ height: 20px;
274
+ background: #6366f1;
275
+ border-radius: 2px;
276
+ animation: wave 1.2s linear infinite;
277
+ }
278
+
279
+ .loading-wave span:nth-child(2) { animation-delay: -1.1s; }
280
+ .loading-wave span:nth-child(3) { animation-delay: -1s; }
281
+ .loading-wave span:nth-child(4) { animation-delay: -0.9s; }
282
+ .loading-wave span:nth-child(5) { animation-delay: -0.8s; }
283
+
284
+ @keyframes wave {
285
+ 0%, 40%, 100% { transform: scaleY(0.4); }
286
+ 20% { transform: scaleY(1); }
287
+ }
288
+
289
+ /* Report Sections */
290
+ .report-section {
291
+ background: #1e1b4b;
292
+ border: 1px solid #312e81;
293
+ border-radius: 12px;
294
+ padding: 1.5rem;
295
+ margin: 1rem 0;
296
+ }
297
+
298
+ .source-badge {
299
+ display: inline-block;
300
+ background: #374151;
301
+ color: #60a5fa;
302
+ padding: 4px 12px;
303
+ border-radius: 16px;
304
+ font-size: 0.875rem;
305
+ margin: 0.25rem;
306
+ }
307
+
308
+ /* Export Section */
309
+ .export-section {
310
+ background: #18181b;
311
+ border: 1px solid #27272a;
312
+ border-radius: 16px;
313
+ padding: 2rem;
314
+ margin-top: 2rem;
315
+ text-align: center;
316
+ }
317
+
318
+ .export-buttons {
319
+ display: flex;
320
+ gap: 1rem;
321
+ justify-content: center;
322
+ margin-top: 1.5rem;
323
+ }
324
+
325
+ .export-btn {
326
+ background: #27272a !important;
327
+ border: 1px solid #3f3f46 !important;
328
+ color: #e4e4e7 !important;
329
+ padding: 0.75rem 1.5rem !important;
330
+ border-radius: 10px !important;
331
+ font-weight: 500 !important;
332
+ cursor: pointer !important;
333
+ display: flex !important;
334
+ align-items: center !important;
335
+ gap: 0.5rem !important;
336
+ }
337
+
338
+ .export-btn:hover {
339
+ background: #374151 !important;
340
+ border-color: #6366f1 !important;
341
+ }
342
+
343
+ /* Responsive */
344
+ @media (max-width: 768px) {
345
+ .header h1 { font-size: 2.5rem; }
346
+ #chatbot .user { margin-left: 10% !important; }
347
+ #chatbot .bot { margin-right: 10% !important; }
348
+ .examples-grid { grid-template-columns: 1fr; }
349
+ }
350
+ """
351
+
352
+ # Initialize models
353
+ config = AgentConfig()
354
+ writer_model, planner_model, embedding_model, reranker, tavily_client = None, None, None, None, None
355
+ IS_PROCESSING = False
356
+
357
+ def initialize_models():
358
+ """Initializes all the models and clients using keys from environment variables."""
359
+ global writer_model, planner_model, embedding_model, reranker, tavily_client, IS_PROCESSING
360
+ try:
361
+ genai.configure(api_key=google_key)
362
+ tavily_client = TavilyClient(api_key=tavily_key)
363
+ writer_model = genai.GenerativeModel(config.WRITER_MODEL)
364
+ planner_model = genai.GenerativeModel(config.WRITER_MODEL)
365
+ embedding_model = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
366
+ reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2', device='cpu')
367
+ except Exception as e:
368
+ print(f"FATAL: Failed to initialize models. Error: {str(e)}")
369
+ raise gr.Error(f"Failed to initialize models. Please check the logs. Error: {str(e)}")
370
+ IS_PROCESSING = False
371
+ print("Models and clients initialized successfully.")
372
+
373
+ # Initialize models on startup
374
+ initialize_models()
375
+
376
+ # Store the last generated report for export
377
+ LAST_REPORT = {"content": "", "timestamp": "", "topic": ""}
378
+
379
+ def format_timestamp():
380
+ """Get formatted timestamp"""
381
+ return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
382
+
383
+ def save_report_to_file(report_content, topic):
384
+ """Save report as markdown file"""
385
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
386
+ filename = f"research_report_{topic.replace(' ', '_')[:30]}_{timestamp}.md"
387
 
388
+ # Add metadata to the report
389
+ full_content = f"""---
390
+ title: {topic}
391
+ generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
392
+ generator: DeepSearch Research Agent
393
+ ---
394
+
395
+ {report_content}
396
+ """
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
+ with open(filename, 'w', encoding='utf-8') as f:
399
+ f.write(full_content)
400
 
401
+ return filename
402
+
403
+ def create_loading_message(stage):
404
+ """Create animated loading message"""
405
+ return f"""
406
+ <div style="display: flex; align-items: center; gap: 12px; color: #a78bfa;">
407
+ <div class="loading-wave">
408
+ <span></span><span></span><span></span><span></span><span></span>
409
+ </div>
410
+ <span style="font-weight: 500;">{stage}</span>
411
+ </div>
412
+ """
413
+
414
+ def format_progress_update(message):
415
+ """Format progress messages with icons"""
416
+ icons = {
417
+ "Step": "🎯",
418
+ "Searching": "πŸ”",
419
+ "Found": "βœ…",
420
+ "Processing": "⚑",
421
+ "Writing": "✍️",
422
+ "Synthesizing": "πŸ§ͺ",
423
+ "Fact-checking": "πŸ”Ž",
424
+ "Indexing": "πŸ“š",
425
+ "Expanding": "πŸ”„"
426
+ }
427
 
428
+ for key, icon in icons.items():
429
+ if key in message:
430
+ return f'<div class="progress-message">{icon} {message}</div>'
431
 
432
+ return f'<div class="progress-message">β–Ά {message}</div>'
 
 
 
 
 
433
 
434
+ def chat_step_wrapper(user_input, history, current_agent_state, topic_state):
435
+ """Enhanced wrapper with better error handling"""
436
+ global IS_PROCESSING, LAST_REPORT
 
 
 
 
437
 
438
+ if IS_PROCESSING:
439
+ print("Ignoring duplicate request while processing.")
440
+ if False: yield
441
+ return
442
 
443
+ IS_PROCESSING = True
444
+ start_time = time.time()
 
 
 
445
 
446
+ try:
447
+ for update in chat_step(user_input, history, current_agent_state, topic_state):
448
+ yield update
449
+ except Exception as e:
450
+ error_msg = f"""
451
+ <div style="background: #7f1d1d; border: 1px solid #dc2626; padding: 1rem; border-radius: 8px; margin: 1rem 0;">
452
+ <strong>❌ Error:</strong> {str(e)}
453
+ <br><br>
454
+ <small>Please check your API keys and try again.</small>
455
+ </div>
456
+ """
457
+ history.append((None, error_msg))
458
+ yield history, "INITIAL", "", gr.update(interactive=True, placeholder="What would you like to research?"), None, gr.update(visible=False)
459
+ finally:
460
+ IS_PROCESSING = False
461
+ elapsed = time.time() - start_time
462
+ print(f"Processing completed in {elapsed:.2f} seconds")
463
+
464
+ def chat_step(user_input, history, current_agent_state, topic_state):
465
+ """Enhanced chat step with rich visual feedback"""
466
+ global LAST_REPORT
467
 
468
+ history = history or []
469
+ history.append((user_input, None))
 
 
 
 
470
 
471
+ if current_agent_state == "INITIAL":
472
+ # Initial topic analysis
473
+ yield history, "CLARIFYING", user_input, gr.update(interactive=False, placeholder="Analyzing topic..."), None, gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
+ history[-1] = (user_input, create_loading_message("Analyzing your research topic"))
476
+ yield history, "CLARIFYING", user_input, gr.update(interactive=False), None, gr.update(visible=False)
 
 
477
 
478
+ time.sleep(0.5) # Brief pause for effect
 
 
479
 
480
+ questions = get_clarifying_questions(planner_model, user_input)
 
 
 
 
 
 
 
 
 
 
481
 
482
+ formatted_response = f"""
483
+ ## 🎯 Research Topic: {user_input}
484
+
485
+ I'll help you create a comprehensive research report on this topic. To ensure I cover exactly what you're looking for, please answer these clarifying questions:
486
+
487
+ {questions}
488
+
489
+ πŸ’‘ **Tip**: The more detailed your answers, the more tailored and comprehensive your report will be!
490
+ """
 
 
 
491
 
492
+ history[-1] = (user_input, formatted_response)
493
+ yield history, "CLARIFYING", user_input, gr.update(interactive=True, placeholder="Type your answers here..."), None, gr.update(visible=False)
494
+
495
+ elif current_agent_state == "CLARIFYING":
496
+ # Start research process
497
+ history[-1] = (user_input, "## πŸš€ Starting Deep Research Process\n\nYour answers have been recorded. Initiating comprehensive research...")
498
+ yield history, "GENERATING", topic_state, gr.update(interactive=False, placeholder="Generating report..."), None, gr.update(visible=False)
499
 
500
+ try:
501
+ # Phase 1: Planning
502
+ history[-1] = (user_input, history[-1][1] + "\n\n" + format_progress_update("Step 1: Creating research plan and outline"))
503
+ yield history, "GENERATING", topic_state, gr.update(interactive=False), None, gr.update(visible=False)
504
+
505
+ plan = research_and_plan(config, planner_model, tavily_client, topic_state, user_input)
506
+
507
+ # Show plan
508
+ sections_list = "\n".join([f" {i+1}. **{s.title}**" for i, s in enumerate(plan['sections'])])
509
+ plan_display = f"""
510
+ ## πŸš€ Starting Deep Research Process
511
 
512
+ ### πŸ“‹ Research Plan Created
513
+
514
+ **Research Focus**: {plan['detailed_topic']}
515
+
516
+ **Report Structure**:
517
+ {sections_list}
518
+
519
+ **Total Sections**: {len(plan['sections'])}
520
+ """
521
+ history[-1] = (user_input, plan_display)
522
+ yield history, "GENERATING", topic_state, gr.update(interactive=False), None, gr.update(visible=False)
523
+
524
+ # Phase 2: Research and Writing
525
+ report_generator = write_report_stream(config, writer_model, tavily_client, embedding_model, reranker, plan)
526
+
527
+ full_report = ""
528
+ progress_display = plan_display
529
+
530
+ for update in report_generator:
531
+ if update.startswith("#"):
532
+ # This is the actual report content
533
+ full_report = update
534
+ LAST_REPORT = {
535
+ "content": full_report,
536
+ "timestamp": format_timestamp(),
537
+ "topic": plan['detailed_topic']
538
+ }
539
+ else:
540
+ # This is a progress update
541
+ progress_display = plan_display + "\n\n---\n\n### πŸ“Š Current Progress\n\n" + format_progress_update(update)
542
+
543
+ # Show preview of report if we have content
544
+ if full_report:
545
+ preview = full_report[:500] + "..." if len(full_report) > 500 else full_report
546
+ progress_display += f"\n\n---\n\n### πŸ“„ Report Preview\n\n{preview}"
547
+
548
+ history[-1] = (user_input, progress_display)
549
+ yield history, "GENERATING", topic_state, gr.update(interactive=False), None, gr.update(visible=False)
550
+
551
+ # Final report display
552
+ completion_msg = f"""
553
+ ## βœ… Research Complete!
554
+
555
+ **Topic**: {plan['detailed_topic']}
556
+ **Generated**: {format_timestamp()}
557
+ **Sections**: {len(plan['sections'])}
558
+
559
+ ---
560
+
561
+ {full_report}
562
+
563
+ ---
564
+
565
+ ### πŸ’Ύ Export Options
566
+
567
+ Your report has been saved and is ready for download. Use the export buttons below to:
568
+ - πŸ“„ Download as Markdown file
569
+ - πŸ“‹ Copy to clipboard
570
+ - πŸ”„ Start a new research topic
571
+ """
572
+
573
+ history[-1] = (user_input, completion_msg)
574
+
575
+ # Save report to file
576
+ report_file = save_report_to_file(full_report, plan['detailed_topic'])
577
+
578
+ yield history, "INITIAL", "", gr.update(interactive=True, placeholder="What would you like to research next?"), report_file, gr.update(visible=True)
579
+
580
+ except Exception as e:
581
+ error_display = f"""
582
+ <div style="background: #7f1d1d; border: 1px solid #dc2626; padding: 1.5rem; border-radius: 12px;">
583
+ <h3 style="color: #fca5a5; margin-top: 0;">❌ Research Error</h3>
584
+ <p style="color: #fecaca;">{str(e)}</p>
585
+ <hr style="border-color: #dc2626; margin: 1rem 0;">
586
+ <p style="color: #fca5a5; font-size: 0.9rem;">Please try again with a different topic or check your API configuration.</p>
587
+ </div>
588
+ """
589
+ history.append((None, error_display))
590
+ yield history, "INITIAL", "", gr.update(interactive=True, placeholder="Let's try again. What would you like to research?"), None, gr.update(visible=False)
591
+
592
+ # Build the interface
593
+ with gr.Blocks(css=CSS, theme=gr.themes.Base()) as app:
594
+ # Header
595
+ gr.HTML("""
596
+ <div class="header">
597
+ <h1>DeepSearch Research Agent</h1>
598
+ <p>AI-Powered Comprehensive Research & Analysis</p>
599
+ </div>
600
+ """)
601
+
602
+ # Status Bar
603
+ gr.HTML("""
604
+ <div class="status-bar">
605
+ <div class="status-indicator">
606
+ <div class="pulse-dot"></div>
607
+ <span style="font-weight: 500;">System Active</span>
608
+ </div>
609
+ <div style="display: flex; gap: 2rem; align-items: center;">
610
+ <span style="color: #71717a;">Powered by Gemini Pro</span>
611
+ <span style="color: #71717a;">Enhanced with Tavily Search</span>
612
+ </div>
613
+ </div>
614
+ """)
615
+
616
+ # State management
617
+ agent_state = gr.State("INITIAL")
618
+ initial_topic_state = gr.State("")
619
+
620
+ # Chat interface
621
+ chatbot = gr.Chatbot(
622
+ elem_id="chatbot",
623
+ bubble_full_width=False,
624
+ height=650,
625
+ visible=True,
626
+ value=[(None, """
627
+ ## πŸ‘‹ Welcome to DeepSearch!
628
+
629
+ I'm your AI research assistant, capable of creating comprehensive, well-researched reports on any topic.
630
+
631
+ ### 🎯 How It Works:
632
+ 1. **Choose a Topic** - Tell me what you want to research
633
+ 2. **Clarify Details** - I'll ask a few questions to understand your needs
634
+ 3. **Deep Research** - I'll search, analyze, and synthesize information
635
+ 4. **Get Your Report** - Receive a detailed, sourced report ready for download
636
+
637
+ ### πŸ’‘ Example Topics:
638
+ - Impact of AI on healthcare
639
+ - Climate change mitigation strategies
640
+ - History of quantum computing
641
+ - Economic effects of remote work
642
+ - Future of renewable energy
643
+
644
+ **Ready to start? Enter your research topic below!**
645
+ """)],
646
+ avatar_images=(None, "πŸ”¬")
647
+ )
648
+
649
+ # Input section
650
+ with gr.Group(elem_classes="input-group"):
651
+ with gr.Row():
652
+ chat_input = gr.Textbox(
653
+ placeholder="What would you like to research today?",
654
+ interactive=True,
655
+ show_label=False,
656
+ scale=8,
657
+ elem_id="user-input"
658
+ )
659
+ submit_button = gr.Button(
660
+ "πŸš€ Start Research",
661
+ scale=2,
662
+ elem_classes="submit-btn"
663
+ )
664
+
665
+ # Examples section
666
+ with gr.Group(elem_classes="examples-container"):
667
+ gr.HTML("<h3>πŸ’‘ Popular Research Topics</h3>")
668
+ gr.Examples(
669
+ examples=[
670
+ "Impact of artificial intelligence on job markets",
671
+ "Sustainable urban development strategies",
672
+ "The psychology of social media addiction",
673
+ "Blockchain technology in supply chain management",
674
+ "Future of space exploration and colonization",
675
+ "Gene editing and CRISPR technology implications"
676
+ ],
677
+ inputs=chat_input,
678
+ label=""
679
+ )
680
+
681
+ # Export section (hidden initially)
682
+ with gr.Group(visible=False, elem_classes="export-section") as export_group:
683
+ gr.HTML("""
684
+ <h3 style="color: #a78bfa; margin-bottom: 0.5rem;">πŸ“₯ Download Your Report</h3>
685
+ <p style="color: #71717a;">Your research report has been generated and saved.</p>
686
+ """)
687
+ report_file = gr.File(
688
+ label="Download Report",
689
+ visible=True,
690
+ file_types=[".md"],
691
+ elem_classes="export-btn"
692
+ )
693
+
694
+ # Event handlers
695
+ submit_event = submit_button.click(
696
+ fn=chat_step_wrapper,
697
+ inputs=[chat_input, chatbot, agent_state, initial_topic_state],
698
+ outputs=[chatbot, agent_state, initial_topic_state, chat_input, report_file, export_group],
699
+ ).then(
700
+ fn=lambda: "",
701
+ outputs=[chat_input],
702
+ queue=False
703
+ )
704
+
705
+ chat_input.submit(
706
+ fn=chat_step_wrapper,
707
+ inputs=[chat_input, chatbot, agent_state, initial_topic_state],
708
+ outputs=[chatbot, agent_state, initial_topic_state, chat_input, report_file, export_group],
709
+ ).then(
710
+ fn=lambda: "",
711
+ outputs=[chat_input],
712
+ queue=False
713
+ )
714
 
715
+ # Launch
716
+ if __name__ == "__main__":
717
+ app.queue()
718
+ app.launch(
719
+ debug=True,
720
+ share=False,
721
+ server_name="0.0.0.0",
722
+ server_port=7860
723
+ )