realtimex-deeptutor 0.5.0.post1__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.
Files changed (145) hide show
  1. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/METADATA +24 -17
  2. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/RECORD +143 -123
  3. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL +1 -1
  4. realtimex_deeptutor-0.5.0.post3.dist-info/entry_points.txt +4 -0
  5. {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/top_level.txt +1 -0
  6. scripts/__init__.py +1 -0
  7. scripts/audit_prompts.py +179 -0
  8. scripts/check_install.py +460 -0
  9. scripts/generate_roster.py +327 -0
  10. scripts/install_all.py +653 -0
  11. scripts/migrate_kb.py +655 -0
  12. scripts/start.py +807 -0
  13. scripts/start_web.py +632 -0
  14. scripts/sync_prompts_from_en.py +147 -0
  15. src/__init__.py +2 -2
  16. src/agents/ideagen/material_organizer_agent.py +2 -0
  17. src/agents/solve/__init__.py +6 -0
  18. src/agents/solve/main_solver.py +9 -0
  19. src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +9 -7
  20. src/agents/solve/session_manager.py +345 -0
  21. src/api/main.py +14 -0
  22. src/api/routers/chat.py +3 -3
  23. src/api/routers/co_writer.py +12 -7
  24. src/api/routers/config.py +1 -0
  25. src/api/routers/guide.py +3 -1
  26. src/api/routers/ideagen.py +7 -0
  27. src/api/routers/knowledge.py +64 -12
  28. src/api/routers/question.py +2 -0
  29. src/api/routers/realtimex.py +137 -0
  30. src/api/routers/research.py +9 -0
  31. src/api/routers/solve.py +120 -2
  32. src/cli/__init__.py +13 -0
  33. src/cli/start.py +209 -0
  34. src/config/constants.py +11 -9
  35. src/knowledge/add_documents.py +453 -213
  36. src/knowledge/extract_numbered_items.py +9 -10
  37. src/knowledge/initializer.py +102 -101
  38. src/knowledge/manager.py +251 -74
  39. src/knowledge/progress_tracker.py +43 -2
  40. src/knowledge/start_kb.py +11 -2
  41. src/logging/__init__.py +5 -0
  42. src/logging/adapters/__init__.py +1 -0
  43. src/logging/adapters/lightrag.py +25 -18
  44. src/logging/adapters/llamaindex.py +1 -0
  45. src/logging/config.py +30 -27
  46. src/logging/handlers/__init__.py +1 -0
  47. src/logging/handlers/console.py +7 -50
  48. src/logging/handlers/file.py +5 -20
  49. src/logging/handlers/websocket.py +23 -19
  50. src/logging/logger.py +161 -126
  51. src/logging/stats/__init__.py +1 -0
  52. src/logging/stats/llm_stats.py +37 -17
  53. src/services/__init__.py +17 -1
  54. src/services/config/__init__.py +1 -0
  55. src/services/config/knowledge_base_config.py +1 -0
  56. src/services/config/loader.py +1 -1
  57. src/services/config/unified_config.py +211 -4
  58. src/services/embedding/__init__.py +1 -0
  59. src/services/embedding/adapters/__init__.py +3 -0
  60. src/services/embedding/adapters/base.py +1 -0
  61. src/services/embedding/adapters/cohere.py +1 -0
  62. src/services/embedding/adapters/jina.py +1 -0
  63. src/services/embedding/adapters/ollama.py +1 -0
  64. src/services/embedding/adapters/openai_compatible.py +1 -0
  65. src/services/embedding/adapters/realtimex.py +125 -0
  66. src/services/embedding/client.py +27 -0
  67. src/services/embedding/config.py +3 -0
  68. src/services/embedding/provider.py +1 -0
  69. src/services/llm/__init__.py +17 -3
  70. src/services/llm/capabilities.py +47 -0
  71. src/services/llm/client.py +32 -0
  72. src/services/llm/cloud_provider.py +21 -4
  73. src/services/llm/config.py +36 -2
  74. src/services/llm/error_mapping.py +1 -0
  75. src/services/llm/exceptions.py +30 -0
  76. src/services/llm/factory.py +55 -16
  77. src/services/llm/local_provider.py +1 -0
  78. src/services/llm/providers/anthropic.py +1 -0
  79. src/services/llm/providers/base_provider.py +1 -0
  80. src/services/llm/providers/open_ai.py +1 -0
  81. src/services/llm/realtimex_provider.py +240 -0
  82. src/services/llm/registry.py +1 -0
  83. src/services/llm/telemetry.py +1 -0
  84. src/services/llm/types.py +1 -0
  85. src/services/llm/utils.py +1 -0
  86. src/services/prompt/__init__.py +1 -0
  87. src/services/prompt/manager.py +3 -2
  88. src/services/rag/__init__.py +27 -5
  89. src/services/rag/components/__init__.py +1 -0
  90. src/services/rag/components/base.py +1 -0
  91. src/services/rag/components/chunkers/__init__.py +1 -0
  92. src/services/rag/components/chunkers/base.py +1 -0
  93. src/services/rag/components/chunkers/fixed.py +1 -0
  94. src/services/rag/components/chunkers/numbered_item.py +1 -0
  95. src/services/rag/components/chunkers/semantic.py +1 -0
  96. src/services/rag/components/embedders/__init__.py +1 -0
  97. src/services/rag/components/embedders/base.py +1 -0
  98. src/services/rag/components/embedders/openai.py +1 -0
  99. src/services/rag/components/indexers/__init__.py +1 -0
  100. src/services/rag/components/indexers/base.py +1 -0
  101. src/services/rag/components/indexers/graph.py +5 -44
  102. src/services/rag/components/indexers/lightrag.py +5 -44
  103. src/services/rag/components/indexers/vector.py +1 -0
  104. src/services/rag/components/parsers/__init__.py +1 -0
  105. src/services/rag/components/parsers/base.py +1 -0
  106. src/services/rag/components/parsers/markdown.py +1 -0
  107. src/services/rag/components/parsers/pdf.py +1 -0
  108. src/services/rag/components/parsers/text.py +1 -0
  109. src/services/rag/components/retrievers/__init__.py +1 -0
  110. src/services/rag/components/retrievers/base.py +1 -0
  111. src/services/rag/components/retrievers/dense.py +1 -0
  112. src/services/rag/components/retrievers/hybrid.py +5 -44
  113. src/services/rag/components/retrievers/lightrag.py +5 -44
  114. src/services/rag/components/routing.py +48 -0
  115. src/services/rag/factory.py +112 -46
  116. src/services/rag/pipeline.py +1 -0
  117. src/services/rag/pipelines/__init__.py +27 -18
  118. src/services/rag/pipelines/lightrag.py +1 -0
  119. src/services/rag/pipelines/llamaindex.py +99 -0
  120. src/services/rag/pipelines/raganything.py +67 -100
  121. src/services/rag/pipelines/raganything_docling.py +368 -0
  122. src/services/rag/service.py +5 -12
  123. src/services/rag/types.py +1 -0
  124. src/services/rag/utils/__init__.py +17 -0
  125. src/services/rag/utils/image_migration.py +279 -0
  126. src/services/search/__init__.py +1 -0
  127. src/services/search/base.py +1 -0
  128. src/services/search/consolidation.py +1 -0
  129. src/services/search/providers/__init__.py +1 -0
  130. src/services/search/providers/baidu.py +1 -0
  131. src/services/search/providers/exa.py +1 -0
  132. src/services/search/providers/jina.py +1 -0
  133. src/services/search/providers/perplexity.py +1 -0
  134. src/services/search/providers/serper.py +1 -0
  135. src/services/search/providers/tavily.py +1 -0
  136. src/services/search/types.py +1 -0
  137. src/services/settings/__init__.py +1 -0
  138. src/services/settings/interface_settings.py +78 -0
  139. src/services/setup/__init__.py +1 -0
  140. src/services/tts/__init__.py +1 -0
  141. src/services/tts/config.py +1 -0
  142. src/utils/realtimex.py +284 -0
  143. realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +0 -2
  144. src/services/rag/pipelines/academic.py +0 -44
  145. {realtimex_deeptutor-0.5.0.post1.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()