realtimex-deeptutor 0.5.0.post2__py3-none-any.whl → 0.5.0.post3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/METADATA +1 -1
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/RECORD +17 -8
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/top_level.txt +1 -0
- scripts/__init__.py +1 -0
- scripts/audit_prompts.py +179 -0
- scripts/check_install.py +460 -0
- scripts/generate_roster.py +327 -0
- scripts/install_all.py +653 -0
- scripts/migrate_kb.py +655 -0
- scripts/start.py +807 -0
- scripts/start_web.py +632 -0
- scripts/sync_prompts_from_en.py +147 -0
- src/cli/start.py +58 -66
- src/services/config/unified_config.py +2 -2
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL +0 -0
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/entry_points.txt +0 -0
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/licenses/LICENSE +0 -0
scripts/start.py
ADDED
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
DeepTutor CLI Launcher
|
|
4
|
+
|
|
5
|
+
Provides a command-line interface for users to easily access:
|
|
6
|
+
1. Solver system (solve_agents)
|
|
7
|
+
2. Question generation system (question_agents)
|
|
8
|
+
3. Deep research system (DR-in-KG)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
# Set Windows console UTF-8 encoding
|
|
18
|
+
if sys.platform == "win32":
|
|
19
|
+
import io
|
|
20
|
+
|
|
21
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
22
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
|
23
|
+
|
|
24
|
+
# Load environment variables
|
|
25
|
+
load_dotenv()
|
|
26
|
+
|
|
27
|
+
# Add project root directory to path
|
|
28
|
+
project_root = Path(__file__).parent.parent
|
|
29
|
+
sys.path.insert(0, str(project_root))
|
|
30
|
+
|
|
31
|
+
from src.agents.question import AgentCoordinator
|
|
32
|
+
from src.agents.solve import MainSolver
|
|
33
|
+
from src.api.utils.history import ActivityType, history_manager
|
|
34
|
+
from src.logging import get_logger
|
|
35
|
+
from src.services.llm import get_llm_config
|
|
36
|
+
|
|
37
|
+
# Initialize logger for CLI
|
|
38
|
+
logger = get_logger("CLI", console_output=True, file_output=True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AITutorStarter:
|
|
42
|
+
"""DeepTutor CLI Launcher"""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
"""Initialize launcher"""
|
|
46
|
+
# Initialize user data directories
|
|
47
|
+
try:
|
|
48
|
+
from src.services.setup import init_user_directories
|
|
49
|
+
|
|
50
|
+
init_user_directories(project_root)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.warning(f"Failed to initialize user directories: {e}")
|
|
53
|
+
logger.info("Continuing anyway...")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
llm_config = get_llm_config()
|
|
57
|
+
self.api_key = llm_config.api_key
|
|
58
|
+
self.base_url = llm_config.base_url
|
|
59
|
+
except ValueError as e:
|
|
60
|
+
logger.error(str(e))
|
|
61
|
+
logger.error("Please configure LLM settings in .env or DeepTutor.env file")
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
# Load knowledge base list
|
|
65
|
+
self.available_kbs = self._load_available_kbs()
|
|
66
|
+
|
|
67
|
+
logger.section("DeepTutor Intelligent Teaching Assistant System")
|
|
68
|
+
logger.success("API configuration loaded")
|
|
69
|
+
logger.info(f"Available knowledge bases: {', '.join(self.available_kbs)}")
|
|
70
|
+
logger.separator()
|
|
71
|
+
|
|
72
|
+
def _load_available_kbs(self) -> list:
|
|
73
|
+
"""Load available knowledge base list"""
|
|
74
|
+
kb_base_dir = Path(__file__).parent / "data/knowledge_bases"
|
|
75
|
+
if not kb_base_dir.exists():
|
|
76
|
+
return ["ai_textbook"] # Default knowledge base
|
|
77
|
+
|
|
78
|
+
# Read configuration file
|
|
79
|
+
kb_config_file = kb_base_dir / "kb_config.json"
|
|
80
|
+
if kb_config_file.exists():
|
|
81
|
+
import json
|
|
82
|
+
|
|
83
|
+
with open(kb_config_file, encoding="utf-8") as f:
|
|
84
|
+
config = json.load(f)
|
|
85
|
+
return list(config.get("knowledge_bases", {}).keys())
|
|
86
|
+
|
|
87
|
+
# Otherwise scan directory
|
|
88
|
+
kbs = []
|
|
89
|
+
for item in kb_base_dir.iterdir():
|
|
90
|
+
if item.is_dir() and not item.name.startswith("."):
|
|
91
|
+
kbs.append(item.name)
|
|
92
|
+
|
|
93
|
+
return kbs if kbs else ["ai_textbook"]
|
|
94
|
+
|
|
95
|
+
def show_main_menu(self) -> str:
|
|
96
|
+
"""Display main menu and get user selection"""
|
|
97
|
+
logger.section("Please select a function:")
|
|
98
|
+
print("1. 🧠 Solver System (Solve) - Intelligent academic problem solving")
|
|
99
|
+
print(
|
|
100
|
+
"2. 📝 Question Generator (Question) - Generate questions based on knowledge base"
|
|
101
|
+
)
|
|
102
|
+
print("3. 🔬 Deep Research (Research) - Multi-round deep knowledge research")
|
|
103
|
+
print("4. 💡 Idea Generation (IdeaGen) - Extract research ideas from notes")
|
|
104
|
+
print("5. 🌐 Start Web Service (Web) - Start frontend and backend services")
|
|
105
|
+
print("6. ⚙️ System Settings (Settings) - View/modify configuration")
|
|
106
|
+
print("7. 🚪 Exit")
|
|
107
|
+
logger.separator()
|
|
108
|
+
|
|
109
|
+
while True:
|
|
110
|
+
choice = input("\nPlease enter option (1-7): ").strip()
|
|
111
|
+
if choice in ["1", "2", "3", "4", "5", "6", "7"]:
|
|
112
|
+
return choice
|
|
113
|
+
logger.warning("Invalid option, please try again")
|
|
114
|
+
|
|
115
|
+
def select_kb(self, default: str = None) -> str:
|
|
116
|
+
"""Select knowledge base"""
|
|
117
|
+
if len(self.available_kbs) == 1:
|
|
118
|
+
return self.available_kbs[0]
|
|
119
|
+
|
|
120
|
+
logger.separator()
|
|
121
|
+
logger.info("Available knowledge bases:")
|
|
122
|
+
logger.separator()
|
|
123
|
+
for i, kb in enumerate(self.available_kbs, 1):
|
|
124
|
+
default_mark = " (default)" if kb == default else ""
|
|
125
|
+
print(f"{i}. {kb}{default_mark}")
|
|
126
|
+
logger.separator()
|
|
127
|
+
|
|
128
|
+
while True:
|
|
129
|
+
choice = input(
|
|
130
|
+
f"\nPlease select knowledge base (1-{len(self.available_kbs)}) [default: 1]: "
|
|
131
|
+
).strip()
|
|
132
|
+
if not choice:
|
|
133
|
+
return self.available_kbs[0]
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
idx = int(choice) - 1
|
|
137
|
+
if 0 <= idx < len(self.available_kbs):
|
|
138
|
+
return self.available_kbs[idx]
|
|
139
|
+
logger.warning(f"Please enter a number between 1 and {len(self.available_kbs)}")
|
|
140
|
+
except ValueError:
|
|
141
|
+
logger.warning("Please enter a valid number")
|
|
142
|
+
|
|
143
|
+
async def run_solve_mode(self):
|
|
144
|
+
"""Run solver mode"""
|
|
145
|
+
logger.section("Solver System")
|
|
146
|
+
|
|
147
|
+
# Select knowledge base
|
|
148
|
+
kb_name = self.select_kb(default="ai_textbook")
|
|
149
|
+
logger.success(f"Selected knowledge base: {kb_name}")
|
|
150
|
+
|
|
151
|
+
# Input question
|
|
152
|
+
logger.separator()
|
|
153
|
+
logger.info(
|
|
154
|
+
"Please enter your question (multi-line input supported, empty line to finish):"
|
|
155
|
+
)
|
|
156
|
+
logger.separator()
|
|
157
|
+
|
|
158
|
+
lines = []
|
|
159
|
+
while True:
|
|
160
|
+
line = input()
|
|
161
|
+
if not line:
|
|
162
|
+
break
|
|
163
|
+
lines.append(line)
|
|
164
|
+
|
|
165
|
+
if not lines:
|
|
166
|
+
logger.warning("No question entered, returning to main menu")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
question = "\n".join(lines)
|
|
170
|
+
|
|
171
|
+
# Display solver mode description
|
|
172
|
+
logger.separator()
|
|
173
|
+
logger.info("Solver Mode: Dual-Loop Architecture")
|
|
174
|
+
logger.separator()
|
|
175
|
+
logger.info("Analysis Loop: Deep understanding of user question")
|
|
176
|
+
logger.info(" Investigate → Note")
|
|
177
|
+
logger.info("")
|
|
178
|
+
logger.info("Solve Loop: Collaborative solving")
|
|
179
|
+
logger.info(" Manager → Solve → Tool → Response → Precision")
|
|
180
|
+
logger.separator()
|
|
181
|
+
|
|
182
|
+
logger.section("Starting solver...")
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
# Create solver
|
|
186
|
+
solver = MainSolver(
|
|
187
|
+
config_path=None, # Use default configuration file
|
|
188
|
+
api_key=self.api_key,
|
|
189
|
+
base_url=self.base_url,
|
|
190
|
+
kb_name=kb_name,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Run solver
|
|
194
|
+
result = await solver.solve(question=question, verbose=True)
|
|
195
|
+
|
|
196
|
+
logger.section("Solving completed!")
|
|
197
|
+
logger.info(f"Output directory: {result['metadata']['output_dir']}")
|
|
198
|
+
|
|
199
|
+
logger.info("Solving statistics:")
|
|
200
|
+
logger.info(f" Execution mode: {result['metadata']['mode']}")
|
|
201
|
+
logger.info(f" Pipeline: {result.get('pipeline', 'dual_loop')}")
|
|
202
|
+
|
|
203
|
+
if "analysis_iterations" in result:
|
|
204
|
+
logger.info(f" Analysis loop iterations: {result['analysis_iterations']} rounds")
|
|
205
|
+
if "solve_steps" in result:
|
|
206
|
+
logger.info(f" Solve steps completed: {result['solve_steps']} steps")
|
|
207
|
+
if "total_steps" in result:
|
|
208
|
+
logger.info(f" Total planned steps: {result['total_steps']}")
|
|
209
|
+
if "citations" in result:
|
|
210
|
+
logger.info(f" Citation count: {len(result['citations'])}")
|
|
211
|
+
|
|
212
|
+
logger.info("Output files:")
|
|
213
|
+
logger.info(f" Markdown: {result['output_md']}")
|
|
214
|
+
logger.info(f" JSON: {result['output_json']}")
|
|
215
|
+
logger.separator()
|
|
216
|
+
|
|
217
|
+
# Display answer preview
|
|
218
|
+
formatted_solution = result.get("formatted_solution", "")
|
|
219
|
+
if formatted_solution:
|
|
220
|
+
logger.separator()
|
|
221
|
+
logger.info("Answer preview:")
|
|
222
|
+
logger.separator()
|
|
223
|
+
preview_lines = formatted_solution.split("\n")[:20]
|
|
224
|
+
preview = "\n".join(preview_lines)
|
|
225
|
+
if len(formatted_solution) > len(preview):
|
|
226
|
+
preview += "\n\n... (more content available in output file) ..."
|
|
227
|
+
print(preview)
|
|
228
|
+
logger.separator()
|
|
229
|
+
|
|
230
|
+
# Save to history
|
|
231
|
+
try:
|
|
232
|
+
history_manager.add_entry(
|
|
233
|
+
activity_type=ActivityType.SOLVE,
|
|
234
|
+
title=question[:50] + "..." if len(question) > 50 else question,
|
|
235
|
+
content={
|
|
236
|
+
"question": question,
|
|
237
|
+
"answer": result.get("formatted_solution", ""),
|
|
238
|
+
"output_dir": result["metadata"]["output_dir"],
|
|
239
|
+
"kb_name": kb_name,
|
|
240
|
+
"metadata": result.get("metadata", {}),
|
|
241
|
+
},
|
|
242
|
+
summary=(
|
|
243
|
+
formatted_solution[:100] + "..."
|
|
244
|
+
if formatted_solution and len(formatted_solution) > 100
|
|
245
|
+
else (formatted_solution or "")
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
except Exception as hist_error:
|
|
249
|
+
# History save failure does not affect main flow
|
|
250
|
+
logger.warning(f"History save failed: {hist_error!s}")
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.section("Solving failed")
|
|
254
|
+
logger.error(str(e))
|
|
255
|
+
import traceback
|
|
256
|
+
|
|
257
|
+
logger.debug("Debug information:")
|
|
258
|
+
logger.debug(traceback.format_exc())
|
|
259
|
+
|
|
260
|
+
async def run_question_mode(self):
|
|
261
|
+
"""Run question generation mode"""
|
|
262
|
+
print("\n" + "=" * 70)
|
|
263
|
+
print("📝 Question Generation System")
|
|
264
|
+
print("=" * 70)
|
|
265
|
+
|
|
266
|
+
# Select knowledge base
|
|
267
|
+
kb_name = self.select_kb(default="ai_textbook")
|
|
268
|
+
print(f"✅ Selected knowledge base: {kb_name}")
|
|
269
|
+
|
|
270
|
+
# Input knowledge point
|
|
271
|
+
print("\n" + "-" * 70)
|
|
272
|
+
print("Please enter knowledge point:")
|
|
273
|
+
print("-" * 70)
|
|
274
|
+
knowledge_point = input().strip()
|
|
275
|
+
|
|
276
|
+
if not knowledge_point:
|
|
277
|
+
print("❌ No knowledge point entered, returning to main menu")
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
# Select difficulty
|
|
281
|
+
print("\n" + "-" * 70)
|
|
282
|
+
print("📊 Difficulty selection:")
|
|
283
|
+
print("-" * 70)
|
|
284
|
+
print("1. Easy")
|
|
285
|
+
print("2. Medium")
|
|
286
|
+
print("3. Hard")
|
|
287
|
+
print("-" * 70)
|
|
288
|
+
|
|
289
|
+
difficulty_map = {"1": "easy", "2": "medium", "3": "hard"}
|
|
290
|
+
while True:
|
|
291
|
+
diff_choice = input("\nPlease select difficulty (1-3) [default: 2]: ").strip()
|
|
292
|
+
if not diff_choice:
|
|
293
|
+
difficulty = "medium"
|
|
294
|
+
break
|
|
295
|
+
if diff_choice in difficulty_map:
|
|
296
|
+
difficulty = difficulty_map[diff_choice]
|
|
297
|
+
break
|
|
298
|
+
print("❌ Invalid option, please try again")
|
|
299
|
+
|
|
300
|
+
# Select question type
|
|
301
|
+
print("\n" + "-" * 70)
|
|
302
|
+
print("📋 Question type selection:")
|
|
303
|
+
print("-" * 70)
|
|
304
|
+
print("1. Multiple choice (choice)")
|
|
305
|
+
print("2. Written answer (written)")
|
|
306
|
+
print("-" * 70)
|
|
307
|
+
|
|
308
|
+
while True:
|
|
309
|
+
type_choice = input("\nPlease select question type (1/2) [default: 1]: ").strip()
|
|
310
|
+
if not type_choice or type_choice == "1":
|
|
311
|
+
question_type = "choice"
|
|
312
|
+
break
|
|
313
|
+
if type_choice == "2":
|
|
314
|
+
question_type = "written"
|
|
315
|
+
break
|
|
316
|
+
print("❌ Invalid option, please try again")
|
|
317
|
+
|
|
318
|
+
print("\n" + "=" * 70)
|
|
319
|
+
print("🚀 Starting question generation...")
|
|
320
|
+
print("=" * 70)
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
# Create coordinator
|
|
324
|
+
coordinator = AgentCoordinator(
|
|
325
|
+
api_key=self.api_key,
|
|
326
|
+
base_url=self.base_url,
|
|
327
|
+
kb_name=kb_name,
|
|
328
|
+
output_dir="./user/question",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Build requirement
|
|
332
|
+
requirement = {
|
|
333
|
+
"knowledge_point": knowledge_point,
|
|
334
|
+
"difficulty": difficulty,
|
|
335
|
+
"question_type": question_type,
|
|
336
|
+
"additional_requirements": "Ensure questions are clear and academically rigorous",
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# Run question generation
|
|
340
|
+
result = await coordinator.generate_question(requirement)
|
|
341
|
+
|
|
342
|
+
if result.get("success"):
|
|
343
|
+
print("\n" + "=" * 70)
|
|
344
|
+
print("✅ Question generation completed!")
|
|
345
|
+
print("=" * 70)
|
|
346
|
+
|
|
347
|
+
question_data = result.get("question", {})
|
|
348
|
+
print("\n📝 Question:")
|
|
349
|
+
print(question_data.get("question", ""))
|
|
350
|
+
|
|
351
|
+
if question_data.get("options"):
|
|
352
|
+
print("\n📋 Options:")
|
|
353
|
+
for key, value in question_data.get("options", {}).items():
|
|
354
|
+
print(f" {key}. {value}")
|
|
355
|
+
|
|
356
|
+
print(f"\n✅ Answer: {question_data.get('correct_answer', '')}")
|
|
357
|
+
print("\n💡 Explanation:")
|
|
358
|
+
print(question_data.get("explanation", ""))
|
|
359
|
+
|
|
360
|
+
if result.get("output_dir"):
|
|
361
|
+
print(f"\n📁 Output directory: {result['output_dir']}")
|
|
362
|
+
|
|
363
|
+
print("=" * 70)
|
|
364
|
+
|
|
365
|
+
# Save to history
|
|
366
|
+
try:
|
|
367
|
+
history_manager.add_entry(
|
|
368
|
+
activity_type=ActivityType.QUESTION,
|
|
369
|
+
title=f"{knowledge_point} ({question_type})",
|
|
370
|
+
content={
|
|
371
|
+
"requirement": requirement,
|
|
372
|
+
"question": question_data,
|
|
373
|
+
"output_dir": result.get("output_dir", ""),
|
|
374
|
+
"kb_name": kb_name,
|
|
375
|
+
},
|
|
376
|
+
summary=(
|
|
377
|
+
question_data.get("question", "")[:100] + "..."
|
|
378
|
+
if len(question_data.get("question", "")) > 100
|
|
379
|
+
else question_data.get("question", "")
|
|
380
|
+
),
|
|
381
|
+
)
|
|
382
|
+
except Exception as hist_error:
|
|
383
|
+
# History save failure does not affect main flow
|
|
384
|
+
print(f"\n⚠️ History save failed: {hist_error!s}")
|
|
385
|
+
else:
|
|
386
|
+
print("\n" + "=" * 70)
|
|
387
|
+
print(f"❌ Question generation failed: {result.get('error', 'Unknown error')}")
|
|
388
|
+
if result.get("reason"):
|
|
389
|
+
print(f"Reason: {result['reason']}")
|
|
390
|
+
print("=" * 70)
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
print("\n" + "=" * 70)
|
|
394
|
+
print(f"❌ Question generation failed: {e!s}")
|
|
395
|
+
import traceback
|
|
396
|
+
|
|
397
|
+
print("\nDebug information:")
|
|
398
|
+
print(traceback.format_exc())
|
|
399
|
+
print("=" * 70)
|
|
400
|
+
|
|
401
|
+
async def run_research_mode(self):
|
|
402
|
+
"""Run deep research mode"""
|
|
403
|
+
print("\n" + "=" * 70)
|
|
404
|
+
print("🔬 Deep Research System")
|
|
405
|
+
print("=" * 70)
|
|
406
|
+
|
|
407
|
+
# Select knowledge base
|
|
408
|
+
kb_name = self.select_kb(default="ai_textbook")
|
|
409
|
+
print(f"✅ Selected knowledge base: {kb_name}")
|
|
410
|
+
|
|
411
|
+
# Input research topic
|
|
412
|
+
print("\n" + "-" * 70)
|
|
413
|
+
print("Please enter research topic:")
|
|
414
|
+
print("-" * 70)
|
|
415
|
+
topic = input().strip()
|
|
416
|
+
|
|
417
|
+
if not topic:
|
|
418
|
+
print("❌ No topic entered, returning to main menu")
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
# Select research mode
|
|
422
|
+
print("\n" + "-" * 70)
|
|
423
|
+
print("🎯 Research mode:")
|
|
424
|
+
print("-" * 70)
|
|
425
|
+
print("1. Quick - 2 subtopics, 2 iterations")
|
|
426
|
+
print("2. Standard - 5 subtopics, 5 iterations")
|
|
427
|
+
print("3. Deep - 8 subtopics, 7 iterations")
|
|
428
|
+
print("4. Auto - Automatically generate subtopic count")
|
|
429
|
+
print("-" * 70)
|
|
430
|
+
|
|
431
|
+
preset_map = {"1": "quick", "2": "standard", "3": "deep", "4": "auto"}
|
|
432
|
+
while True:
|
|
433
|
+
mode_choice = input("\nPlease select mode (1-4) [default: 1]: ").strip()
|
|
434
|
+
if not mode_choice:
|
|
435
|
+
preset = "quick"
|
|
436
|
+
break
|
|
437
|
+
if mode_choice in preset_map:
|
|
438
|
+
preset = preset_map[mode_choice]
|
|
439
|
+
break
|
|
440
|
+
print("❌ Invalid option, please try again")
|
|
441
|
+
|
|
442
|
+
print("\n" + "=" * 70)
|
|
443
|
+
print("🚀 Starting deep research...")
|
|
444
|
+
print("=" * 70)
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
# Import research pipeline
|
|
448
|
+
from src.agents.research.research_pipeline import ResearchPipeline
|
|
449
|
+
from src.services.config import load_config_with_main
|
|
450
|
+
|
|
451
|
+
# Load configuration using unified config loader
|
|
452
|
+
config = load_config_with_main("main.yaml", project_root)
|
|
453
|
+
|
|
454
|
+
# Extract research.* configs to top level (ResearchPipeline expects flat structure)
|
|
455
|
+
research_config = config.get("research", {})
|
|
456
|
+
if "planning" not in config:
|
|
457
|
+
config["planning"] = research_config.get("planning", {}).copy()
|
|
458
|
+
if "researching" not in config:
|
|
459
|
+
config["researching"] = research_config.get("researching", {}).copy()
|
|
460
|
+
if "reporting" not in config:
|
|
461
|
+
config["reporting"] = research_config.get("reporting", {}).copy()
|
|
462
|
+
if "rag" not in config:
|
|
463
|
+
config["rag"] = research_config.get("rag", {}).copy()
|
|
464
|
+
if "queue" not in config:
|
|
465
|
+
config["queue"] = research_config.get("queue", {}).copy()
|
|
466
|
+
if "presets" not in config:
|
|
467
|
+
config["presets"] = research_config.get("presets", {}).copy()
|
|
468
|
+
|
|
469
|
+
# Set output paths
|
|
470
|
+
if "system" not in config:
|
|
471
|
+
config["system"] = {}
|
|
472
|
+
output_base = project_root / "data" / "user" / "research"
|
|
473
|
+
config["system"]["output_base_dir"] = str(output_base / "cache")
|
|
474
|
+
config["system"]["reports_dir"] = str(output_base / "reports")
|
|
475
|
+
|
|
476
|
+
# Apply preset mode configuration
|
|
477
|
+
preset_configs = {
|
|
478
|
+
"quick": {
|
|
479
|
+
"planning": {"decompose": {"initial_subtopics": 2, "mode": "manual"}},
|
|
480
|
+
"researching": {"max_iterations": 2},
|
|
481
|
+
},
|
|
482
|
+
"standard": {
|
|
483
|
+
"planning": {"decompose": {"initial_subtopics": 5, "mode": "manual"}},
|
|
484
|
+
"researching": {"max_iterations": 5},
|
|
485
|
+
},
|
|
486
|
+
"deep": {
|
|
487
|
+
"planning": {"decompose": {"initial_subtopics": 8, "mode": "manual"}},
|
|
488
|
+
"researching": {"max_iterations": 7},
|
|
489
|
+
},
|
|
490
|
+
"auto": {
|
|
491
|
+
"planning": {"decompose": {"mode": "auto", "auto_max_subtopics": 8}},
|
|
492
|
+
"researching": {"max_iterations": 6},
|
|
493
|
+
},
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if preset in preset_configs:
|
|
497
|
+
preset_cfg = preset_configs[preset]
|
|
498
|
+
# Apply planning configuration
|
|
499
|
+
if "planning" in preset_cfg:
|
|
500
|
+
for key, value in preset_cfg["planning"].items():
|
|
501
|
+
if key not in config["planning"]:
|
|
502
|
+
config["planning"][key] = {}
|
|
503
|
+
config["planning"][key].update(value)
|
|
504
|
+
# Apply researching configuration
|
|
505
|
+
if "researching" in preset_cfg:
|
|
506
|
+
config["researching"].update(preset_cfg["researching"])
|
|
507
|
+
|
|
508
|
+
if preset == "auto":
|
|
509
|
+
print(
|
|
510
|
+
f"✅ Auto mode enabled (automatically generate subtopic count, max: {config['planning']['decompose'].get('auto_max_subtopics', 8)})"
|
|
511
|
+
)
|
|
512
|
+
else:
|
|
513
|
+
print(f"✅ {preset.capitalize()} mode enabled")
|
|
514
|
+
|
|
515
|
+
# Create research pipeline
|
|
516
|
+
pipeline = ResearchPipeline(
|
|
517
|
+
config=config, api_key=self.api_key, base_url=self.base_url, kb_name=kb_name
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
# Execute research
|
|
521
|
+
result = await pipeline.run(topic)
|
|
522
|
+
|
|
523
|
+
print("\n" + "=" * 70)
|
|
524
|
+
print("✅ Research completed!")
|
|
525
|
+
print("=" * 70)
|
|
526
|
+
print(f"📁 Report location: {result['final_report_path']}")
|
|
527
|
+
print(f"📊 Research ID: {result['research_id']}")
|
|
528
|
+
|
|
529
|
+
metadata = result.get("metadata", {})
|
|
530
|
+
if metadata:
|
|
531
|
+
print("\n📈 Research statistics:")
|
|
532
|
+
print(f" Report word count: {metadata.get('report_word_count', 0)}")
|
|
533
|
+
stats = metadata.get("statistics", {})
|
|
534
|
+
if stats:
|
|
535
|
+
print(f" Topic blocks: {stats.get('total_blocks', 0)}")
|
|
536
|
+
print(f" Completed topics: {stats.get('completed', 0)}")
|
|
537
|
+
print(f" Tool calls: {stats.get('total_tool_calls', 0)}")
|
|
538
|
+
|
|
539
|
+
print("=" * 70)
|
|
540
|
+
|
|
541
|
+
# Save to history
|
|
542
|
+
try:
|
|
543
|
+
# Read report content
|
|
544
|
+
report_content = ""
|
|
545
|
+
if result.get("final_report_path") and Path(result["final_report_path"]).exists():
|
|
546
|
+
with open(result["final_report_path"], encoding="utf-8") as f:
|
|
547
|
+
report_content = f.read()
|
|
548
|
+
|
|
549
|
+
history_manager.add_entry(
|
|
550
|
+
activity_type=ActivityType.RESEARCH,
|
|
551
|
+
title=topic,
|
|
552
|
+
content={
|
|
553
|
+
"topic": topic,
|
|
554
|
+
"report": report_content,
|
|
555
|
+
"report_path": result.get("final_report_path", ""),
|
|
556
|
+
"research_id": result.get("research_id", ""),
|
|
557
|
+
"kb_name": kb_name,
|
|
558
|
+
"metadata": metadata,
|
|
559
|
+
},
|
|
560
|
+
summary=(
|
|
561
|
+
report_content[:200] + "..."
|
|
562
|
+
if len(report_content) > 200
|
|
563
|
+
else report_content
|
|
564
|
+
),
|
|
565
|
+
)
|
|
566
|
+
except Exception as hist_error:
|
|
567
|
+
# History save failure does not affect main flow
|
|
568
|
+
logger.warning(f"History save failed: {hist_error!s}")
|
|
569
|
+
|
|
570
|
+
except Exception as e:
|
|
571
|
+
logger.section("Research failed")
|
|
572
|
+
logger.error(str(e))
|
|
573
|
+
import traceback
|
|
574
|
+
|
|
575
|
+
logger.debug("Debug information:")
|
|
576
|
+
logger.debug(traceback.format_exc())
|
|
577
|
+
|
|
578
|
+
async def run_ideagen_mode(self):
|
|
579
|
+
"""Run idea generation mode"""
|
|
580
|
+
print("\n" + "=" * 70)
|
|
581
|
+
print("💡 Idea Generation System")
|
|
582
|
+
print("=" * 70)
|
|
583
|
+
|
|
584
|
+
# Select knowledge base
|
|
585
|
+
kb_name = self.select_kb()
|
|
586
|
+
print(f"✅ Selected knowledge base: {kb_name}")
|
|
587
|
+
|
|
588
|
+
# Input materials
|
|
589
|
+
print("\n" + "-" * 70)
|
|
590
|
+
print(
|
|
591
|
+
"Please enter knowledge points or material content (multi-line input supported, empty line to finish):"
|
|
592
|
+
)
|
|
593
|
+
print("-" * 70)
|
|
594
|
+
|
|
595
|
+
lines = []
|
|
596
|
+
while True:
|
|
597
|
+
line = input()
|
|
598
|
+
if not line:
|
|
599
|
+
break
|
|
600
|
+
lines.append(line)
|
|
601
|
+
|
|
602
|
+
if not lines:
|
|
603
|
+
print("❌ No content entered, returning to main menu")
|
|
604
|
+
return
|
|
605
|
+
|
|
606
|
+
materials = "\n".join(lines)
|
|
607
|
+
|
|
608
|
+
print("\n" + "=" * 70)
|
|
609
|
+
print("🚀 Starting research idea generation...")
|
|
610
|
+
print("=" * 70)
|
|
611
|
+
|
|
612
|
+
try:
|
|
613
|
+
# Import ideagen module
|
|
614
|
+
from src.agents.ideagen.idea_generation_workflow import IdeaGenerationWorkflow
|
|
615
|
+
from src.agents.ideagen.material_organizer_agent import MaterialOrganizerAgent
|
|
616
|
+
|
|
617
|
+
# Organize materials
|
|
618
|
+
organizer = MaterialOrganizerAgent(api_key=self.api_key, base_url=self.base_url)
|
|
619
|
+
|
|
620
|
+
print("📊 Extracting knowledge points...")
|
|
621
|
+
knowledge_points = await organizer.extract_knowledge_points(materials)
|
|
622
|
+
print(f"✅ Extracted {len(knowledge_points)} knowledge points")
|
|
623
|
+
|
|
624
|
+
if not knowledge_points:
|
|
625
|
+
print("❌ Failed to extract valid knowledge points")
|
|
626
|
+
return
|
|
627
|
+
|
|
628
|
+
# Generate ideas
|
|
629
|
+
workflow = IdeaGenerationWorkflow(api_key=self.api_key, base_url=self.base_url)
|
|
630
|
+
|
|
631
|
+
print("🔍 Generating research ideas...")
|
|
632
|
+
result = await workflow.process(knowledge_points)
|
|
633
|
+
|
|
634
|
+
print("\n" + "=" * 70)
|
|
635
|
+
print("✅ Research idea generation completed!")
|
|
636
|
+
print("=" * 70)
|
|
637
|
+
print(result)
|
|
638
|
+
print("=" * 70)
|
|
639
|
+
|
|
640
|
+
except Exception as e:
|
|
641
|
+
logger.section("Generation failed")
|
|
642
|
+
logger.error(str(e))
|
|
643
|
+
import traceback
|
|
644
|
+
|
|
645
|
+
logger.debug("Debug information:")
|
|
646
|
+
logger.debug(traceback.format_exc())
|
|
647
|
+
|
|
648
|
+
def run_web_mode(self):
|
|
649
|
+
"""Start web service"""
|
|
650
|
+
print("\n" + "=" * 70)
|
|
651
|
+
print("🌐 Start Web Service")
|
|
652
|
+
print("=" * 70)
|
|
653
|
+
print("1. Start backend API only (port 8000)")
|
|
654
|
+
print("2. Start frontend only (port 3000)")
|
|
655
|
+
print("3. Start both frontend and backend")
|
|
656
|
+
print("4. Return to main menu")
|
|
657
|
+
print("=" * 70)
|
|
658
|
+
|
|
659
|
+
while True:
|
|
660
|
+
choice = input("\nPlease select (1-4): ").strip()
|
|
661
|
+
if choice == "1":
|
|
662
|
+
print("\n🚀 Starting backend service...")
|
|
663
|
+
print("Command: python -m uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload")
|
|
664
|
+
print("-" * 70)
|
|
665
|
+
import subprocess
|
|
666
|
+
|
|
667
|
+
subprocess.run(
|
|
668
|
+
[
|
|
669
|
+
sys.executable,
|
|
670
|
+
"-m",
|
|
671
|
+
"uvicorn",
|
|
672
|
+
"api.main:app",
|
|
673
|
+
"--host",
|
|
674
|
+
"0.0.0.0",
|
|
675
|
+
"--port",
|
|
676
|
+
"8000",
|
|
677
|
+
"--reload",
|
|
678
|
+
],
|
|
679
|
+
check=False,
|
|
680
|
+
)
|
|
681
|
+
break
|
|
682
|
+
if choice == "2":
|
|
683
|
+
print("\n🚀 Starting frontend service...")
|
|
684
|
+
print("Command: cd web && npm run dev")
|
|
685
|
+
print("-" * 70)
|
|
686
|
+
import subprocess
|
|
687
|
+
|
|
688
|
+
web_dir = Path(__file__).parent / "web"
|
|
689
|
+
subprocess.run(["npm", "run", "dev"], check=False, cwd=web_dir)
|
|
690
|
+
break
|
|
691
|
+
if choice == "3":
|
|
692
|
+
print("\n🚀 Starting both frontend and backend services...")
|
|
693
|
+
print("Command: python start_web.py")
|
|
694
|
+
print("-" * 70)
|
|
695
|
+
import subprocess
|
|
696
|
+
|
|
697
|
+
subprocess.run([sys.executable, "start_web.py"], check=False)
|
|
698
|
+
break
|
|
699
|
+
if choice == "4":
|
|
700
|
+
return
|
|
701
|
+
print("❌ Invalid option, please try again")
|
|
702
|
+
|
|
703
|
+
def show_settings(self):
|
|
704
|
+
"""Display settings"""
|
|
705
|
+
print("\n" + "=" * 70)
|
|
706
|
+
print("⚙️ System Settings")
|
|
707
|
+
print("=" * 70)
|
|
708
|
+
|
|
709
|
+
# Display LLM configuration
|
|
710
|
+
try:
|
|
711
|
+
llm_config = get_llm_config()
|
|
712
|
+
print("\n📦 LLM Configuration:")
|
|
713
|
+
print(f" Model: {llm_config.model or 'N/A'}")
|
|
714
|
+
print(f" API Endpoint: {llm_config.base_url or 'N/A'}")
|
|
715
|
+
print(f" API Key: {'Configured' if llm_config.api_key else 'Not configured'}")
|
|
716
|
+
except Exception as e:
|
|
717
|
+
print(f" ❌ Load failed: {e}")
|
|
718
|
+
|
|
719
|
+
# Display knowledge bases
|
|
720
|
+
print("\n📚 Available knowledge bases:")
|
|
721
|
+
for i, kb in enumerate(self.available_kbs, 1):
|
|
722
|
+
print(f" {i}. {kb}")
|
|
723
|
+
|
|
724
|
+
# Display configuration file locations
|
|
725
|
+
print("\n📁 Configuration file locations:")
|
|
726
|
+
env_files = [".env", "DeepTutor.env"]
|
|
727
|
+
for env_file in env_files:
|
|
728
|
+
env_path = Path(__file__).parent / env_file
|
|
729
|
+
if env_path.exists():
|
|
730
|
+
print(f" ✅ {env_path}")
|
|
731
|
+
else:
|
|
732
|
+
print(f" ⚪ {env_path} (not found)")
|
|
733
|
+
|
|
734
|
+
print("\n" + "=" * 70)
|
|
735
|
+
print(
|
|
736
|
+
"💡 Tip: To modify settings, edit the .env file directly, or use the Settings page in the Web interface"
|
|
737
|
+
)
|
|
738
|
+
print("=" * 70)
|
|
739
|
+
|
|
740
|
+
input("\nPress Enter to return to main menu...")
|
|
741
|
+
|
|
742
|
+
async def run(self):
|
|
743
|
+
"""Run main loop"""
|
|
744
|
+
while True:
|
|
745
|
+
try:
|
|
746
|
+
choice = self.show_main_menu()
|
|
747
|
+
|
|
748
|
+
if choice == "1":
|
|
749
|
+
await self.run_solve_mode()
|
|
750
|
+
elif choice == "2":
|
|
751
|
+
await self.run_question_mode()
|
|
752
|
+
elif choice == "3":
|
|
753
|
+
await self.run_research_mode()
|
|
754
|
+
elif choice == "4":
|
|
755
|
+
await self.run_ideagen_mode()
|
|
756
|
+
elif choice == "5":
|
|
757
|
+
self.run_web_mode()
|
|
758
|
+
continue # Don't ask to continue after web mode
|
|
759
|
+
elif choice == "6":
|
|
760
|
+
self.show_settings()
|
|
761
|
+
continue # Don't ask to continue after settings
|
|
762
|
+
elif choice == "7":
|
|
763
|
+
print("\n" + "=" * 70)
|
|
764
|
+
print("👋 Thank you for using DeepTutor Intelligent Teaching Assistant System!")
|
|
765
|
+
print("=" * 70)
|
|
766
|
+
break
|
|
767
|
+
|
|
768
|
+
# Ask if continue
|
|
769
|
+
print("\n" + "-" * 70)
|
|
770
|
+
continue_choice = input("Continue using? (y/n) [default: y]: ").strip().lower()
|
|
771
|
+
if continue_choice == "n":
|
|
772
|
+
print("\n" + "=" * 70)
|
|
773
|
+
print("👋 Thank you for using DeepTutor Intelligent Teaching Assistant System!")
|
|
774
|
+
print("=" * 70)
|
|
775
|
+
break
|
|
776
|
+
|
|
777
|
+
except KeyboardInterrupt:
|
|
778
|
+
print("\n\n" + "=" * 70)
|
|
779
|
+
print("👋 Program interrupted, thank you for using!")
|
|
780
|
+
print("=" * 70)
|
|
781
|
+
break
|
|
782
|
+
except Exception as e:
|
|
783
|
+
print(f"\n❌ Error occurred: {e!s}")
|
|
784
|
+
print("Please retry or exit the program")
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def main():
|
|
788
|
+
"""Main function"""
|
|
789
|
+
try:
|
|
790
|
+
starter = AITutorStarter()
|
|
791
|
+
asyncio.run(starter.run())
|
|
792
|
+
except Exception as e:
|
|
793
|
+
logger.error(f"Startup failed: {e!s}")
|
|
794
|
+
sys.exit(1)
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
if __name__ == "__main__":
|
|
798
|
+
# Initialize user data directories
|
|
799
|
+
try:
|
|
800
|
+
from src.services.setup import init_user_directories
|
|
801
|
+
|
|
802
|
+
init_user_directories(project_root)
|
|
803
|
+
except Exception as e:
|
|
804
|
+
logger.warning(f"Failed to initialize user directories: {e}")
|
|
805
|
+
logger.info("Continuing anyway...")
|
|
806
|
+
|
|
807
|
+
main()
|