realtimex-deeptutor 0.5.0.post1__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 (276) hide show
  1. realtimex_deeptutor/__init__.py +67 -0
  2. realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
  3. realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
  4. realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
  5. realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
  6. realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
  7. realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
  8. src/__init__.py +40 -0
  9. src/agents/__init__.py +24 -0
  10. src/agents/base_agent.py +657 -0
  11. src/agents/chat/__init__.py +24 -0
  12. src/agents/chat/chat_agent.py +435 -0
  13. src/agents/chat/prompts/en/chat_agent.yaml +35 -0
  14. src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
  15. src/agents/chat/session_manager.py +311 -0
  16. src/agents/co_writer/__init__.py +0 -0
  17. src/agents/co_writer/edit_agent.py +260 -0
  18. src/agents/co_writer/narrator_agent.py +423 -0
  19. src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
  20. src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
  21. src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
  22. src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
  23. src/agents/guide/__init__.py +16 -0
  24. src/agents/guide/agents/__init__.py +11 -0
  25. src/agents/guide/agents/chat_agent.py +104 -0
  26. src/agents/guide/agents/interactive_agent.py +223 -0
  27. src/agents/guide/agents/locate_agent.py +149 -0
  28. src/agents/guide/agents/summary_agent.py +150 -0
  29. src/agents/guide/guide_manager.py +500 -0
  30. src/agents/guide/prompts/en/chat_agent.yaml +41 -0
  31. src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
  32. src/agents/guide/prompts/en/locate_agent.yaml +68 -0
  33. src/agents/guide/prompts/en/summary_agent.yaml +157 -0
  34. src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
  35. src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
  36. src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
  37. src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
  38. src/agents/ideagen/__init__.py +12 -0
  39. src/agents/ideagen/idea_generation_workflow.py +426 -0
  40. src/agents/ideagen/material_organizer_agent.py +173 -0
  41. src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
  42. src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
  43. src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
  44. src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
  45. src/agents/question/__init__.py +24 -0
  46. src/agents/question/agents/__init__.py +18 -0
  47. src/agents/question/agents/generate_agent.py +381 -0
  48. src/agents/question/agents/relevance_analyzer.py +207 -0
  49. src/agents/question/agents/retrieve_agent.py +239 -0
  50. src/agents/question/coordinator.py +718 -0
  51. src/agents/question/example.py +109 -0
  52. src/agents/question/prompts/en/coordinator.yaml +75 -0
  53. src/agents/question/prompts/en/generate_agent.yaml +77 -0
  54. src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
  55. src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
  56. src/agents/question/prompts/zh/coordinator.yaml +75 -0
  57. src/agents/question/prompts/zh/generate_agent.yaml +77 -0
  58. src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
  59. src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
  60. src/agents/research/agents/__init__.py +23 -0
  61. src/agents/research/agents/decompose_agent.py +507 -0
  62. src/agents/research/agents/manager_agent.py +228 -0
  63. src/agents/research/agents/note_agent.py +180 -0
  64. src/agents/research/agents/rephrase_agent.py +263 -0
  65. src/agents/research/agents/reporting_agent.py +1333 -0
  66. src/agents/research/agents/research_agent.py +714 -0
  67. src/agents/research/data_structures.py +451 -0
  68. src/agents/research/main.py +188 -0
  69. src/agents/research/prompts/en/decompose_agent.yaml +89 -0
  70. src/agents/research/prompts/en/manager_agent.yaml +24 -0
  71. src/agents/research/prompts/en/note_agent.yaml +121 -0
  72. src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
  73. src/agents/research/prompts/en/reporting_agent.yaml +380 -0
  74. src/agents/research/prompts/en/research_agent.yaml +173 -0
  75. src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
  76. src/agents/research/prompts/zh/manager_agent.yaml +24 -0
  77. src/agents/research/prompts/zh/note_agent.yaml +121 -0
  78. src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
  79. src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
  80. src/agents/research/prompts/zh/research_agent.yaml +173 -0
  81. src/agents/research/research_pipeline.py +1309 -0
  82. src/agents/research/utils/__init__.py +60 -0
  83. src/agents/research/utils/citation_manager.py +799 -0
  84. src/agents/research/utils/json_utils.py +98 -0
  85. src/agents/research/utils/token_tracker.py +297 -0
  86. src/agents/solve/__init__.py +80 -0
  87. src/agents/solve/analysis_loop/__init__.py +14 -0
  88. src/agents/solve/analysis_loop/investigate_agent.py +414 -0
  89. src/agents/solve/analysis_loop/note_agent.py +190 -0
  90. src/agents/solve/main_solver.py +862 -0
  91. src/agents/solve/memory/__init__.py +34 -0
  92. src/agents/solve/memory/citation_memory.py +353 -0
  93. src/agents/solve/memory/investigate_memory.py +226 -0
  94. src/agents/solve/memory/solve_memory.py +340 -0
  95. src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
  96. src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
  97. src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
  98. src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
  99. src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
  100. src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
  101. src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
  102. src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
  103. src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
  104. src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
  105. src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
  106. src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
  107. src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
  108. src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
  109. src/agents/solve/solve_loop/__init__.py +22 -0
  110. src/agents/solve/solve_loop/citation_manager.py +74 -0
  111. src/agents/solve/solve_loop/manager_agent.py +274 -0
  112. src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
  113. src/agents/solve/solve_loop/response_agent.py +301 -0
  114. src/agents/solve/solve_loop/solve_agent.py +325 -0
  115. src/agents/solve/solve_loop/tool_agent.py +470 -0
  116. src/agents/solve/utils/__init__.py +64 -0
  117. src/agents/solve/utils/config_validator.py +313 -0
  118. src/agents/solve/utils/display_manager.py +223 -0
  119. src/agents/solve/utils/error_handler.py +363 -0
  120. src/agents/solve/utils/json_utils.py +98 -0
  121. src/agents/solve/utils/performance_monitor.py +407 -0
  122. src/agents/solve/utils/token_tracker.py +541 -0
  123. src/api/__init__.py +0 -0
  124. src/api/main.py +240 -0
  125. src/api/routers/__init__.py +1 -0
  126. src/api/routers/agent_config.py +69 -0
  127. src/api/routers/chat.py +296 -0
  128. src/api/routers/co_writer.py +337 -0
  129. src/api/routers/config.py +627 -0
  130. src/api/routers/dashboard.py +18 -0
  131. src/api/routers/guide.py +337 -0
  132. src/api/routers/ideagen.py +436 -0
  133. src/api/routers/knowledge.py +821 -0
  134. src/api/routers/notebook.py +247 -0
  135. src/api/routers/question.py +537 -0
  136. src/api/routers/research.py +394 -0
  137. src/api/routers/settings.py +164 -0
  138. src/api/routers/solve.py +305 -0
  139. src/api/routers/system.py +252 -0
  140. src/api/run_server.py +61 -0
  141. src/api/utils/history.py +172 -0
  142. src/api/utils/log_interceptor.py +21 -0
  143. src/api/utils/notebook_manager.py +415 -0
  144. src/api/utils/progress_broadcaster.py +72 -0
  145. src/api/utils/task_id_manager.py +100 -0
  146. src/config/__init__.py +0 -0
  147. src/config/accessors.py +18 -0
  148. src/config/constants.py +34 -0
  149. src/config/defaults.py +18 -0
  150. src/config/schema.py +38 -0
  151. src/config/settings.py +50 -0
  152. src/core/errors.py +62 -0
  153. src/knowledge/__init__.py +23 -0
  154. src/knowledge/add_documents.py +606 -0
  155. src/knowledge/config.py +65 -0
  156. src/knowledge/example_add_documents.py +236 -0
  157. src/knowledge/extract_numbered_items.py +1039 -0
  158. src/knowledge/initializer.py +621 -0
  159. src/knowledge/kb.py +22 -0
  160. src/knowledge/manager.py +782 -0
  161. src/knowledge/progress_tracker.py +182 -0
  162. src/knowledge/start_kb.py +535 -0
  163. src/logging/__init__.py +103 -0
  164. src/logging/adapters/__init__.py +17 -0
  165. src/logging/adapters/lightrag.py +184 -0
  166. src/logging/adapters/llamaindex.py +141 -0
  167. src/logging/config.py +80 -0
  168. src/logging/handlers/__init__.py +20 -0
  169. src/logging/handlers/console.py +75 -0
  170. src/logging/handlers/file.py +201 -0
  171. src/logging/handlers/websocket.py +127 -0
  172. src/logging/logger.py +709 -0
  173. src/logging/stats/__init__.py +16 -0
  174. src/logging/stats/llm_stats.py +179 -0
  175. src/services/__init__.py +56 -0
  176. src/services/config/__init__.py +61 -0
  177. src/services/config/knowledge_base_config.py +210 -0
  178. src/services/config/loader.py +260 -0
  179. src/services/config/unified_config.py +603 -0
  180. src/services/embedding/__init__.py +45 -0
  181. src/services/embedding/adapters/__init__.py +22 -0
  182. src/services/embedding/adapters/base.py +106 -0
  183. src/services/embedding/adapters/cohere.py +127 -0
  184. src/services/embedding/adapters/jina.py +99 -0
  185. src/services/embedding/adapters/ollama.py +116 -0
  186. src/services/embedding/adapters/openai_compatible.py +96 -0
  187. src/services/embedding/client.py +159 -0
  188. src/services/embedding/config.py +156 -0
  189. src/services/embedding/provider.py +119 -0
  190. src/services/llm/__init__.py +152 -0
  191. src/services/llm/capabilities.py +313 -0
  192. src/services/llm/client.py +302 -0
  193. src/services/llm/cloud_provider.py +530 -0
  194. src/services/llm/config.py +200 -0
  195. src/services/llm/error_mapping.py +103 -0
  196. src/services/llm/exceptions.py +152 -0
  197. src/services/llm/factory.py +450 -0
  198. src/services/llm/local_provider.py +347 -0
  199. src/services/llm/providers/anthropic.py +95 -0
  200. src/services/llm/providers/base_provider.py +93 -0
  201. src/services/llm/providers/open_ai.py +83 -0
  202. src/services/llm/registry.py +71 -0
  203. src/services/llm/telemetry.py +40 -0
  204. src/services/llm/types.py +27 -0
  205. src/services/llm/utils.py +333 -0
  206. src/services/prompt/__init__.py +25 -0
  207. src/services/prompt/manager.py +206 -0
  208. src/services/rag/__init__.py +64 -0
  209. src/services/rag/components/__init__.py +29 -0
  210. src/services/rag/components/base.py +59 -0
  211. src/services/rag/components/chunkers/__init__.py +18 -0
  212. src/services/rag/components/chunkers/base.py +34 -0
  213. src/services/rag/components/chunkers/fixed.py +71 -0
  214. src/services/rag/components/chunkers/numbered_item.py +94 -0
  215. src/services/rag/components/chunkers/semantic.py +97 -0
  216. src/services/rag/components/embedders/__init__.py +14 -0
  217. src/services/rag/components/embedders/base.py +32 -0
  218. src/services/rag/components/embedders/openai.py +63 -0
  219. src/services/rag/components/indexers/__init__.py +18 -0
  220. src/services/rag/components/indexers/base.py +35 -0
  221. src/services/rag/components/indexers/graph.py +172 -0
  222. src/services/rag/components/indexers/lightrag.py +156 -0
  223. src/services/rag/components/indexers/vector.py +146 -0
  224. src/services/rag/components/parsers/__init__.py +18 -0
  225. src/services/rag/components/parsers/base.py +35 -0
  226. src/services/rag/components/parsers/markdown.py +52 -0
  227. src/services/rag/components/parsers/pdf.py +115 -0
  228. src/services/rag/components/parsers/text.py +86 -0
  229. src/services/rag/components/retrievers/__init__.py +18 -0
  230. src/services/rag/components/retrievers/base.py +34 -0
  231. src/services/rag/components/retrievers/dense.py +200 -0
  232. src/services/rag/components/retrievers/hybrid.py +164 -0
  233. src/services/rag/components/retrievers/lightrag.py +169 -0
  234. src/services/rag/components/routing.py +286 -0
  235. src/services/rag/factory.py +234 -0
  236. src/services/rag/pipeline.py +215 -0
  237. src/services/rag/pipelines/__init__.py +32 -0
  238. src/services/rag/pipelines/academic.py +44 -0
  239. src/services/rag/pipelines/lightrag.py +43 -0
  240. src/services/rag/pipelines/llamaindex.py +313 -0
  241. src/services/rag/pipelines/raganything.py +384 -0
  242. src/services/rag/service.py +244 -0
  243. src/services/rag/types.py +73 -0
  244. src/services/search/__init__.py +284 -0
  245. src/services/search/base.py +87 -0
  246. src/services/search/consolidation.py +398 -0
  247. src/services/search/providers/__init__.py +128 -0
  248. src/services/search/providers/baidu.py +188 -0
  249. src/services/search/providers/exa.py +194 -0
  250. src/services/search/providers/jina.py +161 -0
  251. src/services/search/providers/perplexity.py +153 -0
  252. src/services/search/providers/serper.py +209 -0
  253. src/services/search/providers/tavily.py +161 -0
  254. src/services/search/types.py +114 -0
  255. src/services/setup/__init__.py +34 -0
  256. src/services/setup/init.py +285 -0
  257. src/services/tts/__init__.py +16 -0
  258. src/services/tts/config.py +99 -0
  259. src/tools/__init__.py +91 -0
  260. src/tools/code_executor.py +536 -0
  261. src/tools/paper_search_tool.py +171 -0
  262. src/tools/query_item_tool.py +310 -0
  263. src/tools/question/__init__.py +15 -0
  264. src/tools/question/exam_mimic.py +616 -0
  265. src/tools/question/pdf_parser.py +211 -0
  266. src/tools/question/question_extractor.py +397 -0
  267. src/tools/rag_tool.py +173 -0
  268. src/tools/tex_chunker.py +339 -0
  269. src/tools/tex_downloader.py +253 -0
  270. src/tools/web_search.py +71 -0
  271. src/utils/config_manager.py +206 -0
  272. src/utils/document_validator.py +168 -0
  273. src/utils/error_rate_tracker.py +111 -0
  274. src/utils/error_utils.py +82 -0
  275. src/utils/json_parser.py +110 -0
  276. src/utils/network/circuit_breaker.py +79 -0
@@ -0,0 +1,507 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ DecomposeAgent - Topic decomposition Agent
5
+ Responsible for decomposing topics into multiple subtopics and generating overviews for each subtopic
6
+ """
7
+
8
+ from pathlib import Path
9
+ import sys
10
+ from typing import Any
11
+
12
+ project_root = Path(__file__).parent.parent.parent.parent
13
+ sys.path.insert(0, str(project_root))
14
+
15
+ import json
16
+
17
+ from src.agents.base_agent import BaseAgent
18
+ from src.agents.research.data_structures import ToolTrace
19
+ from src.tools.rag_tool import rag_search
20
+
21
+ from ..utils.json_utils import extract_json_from_text
22
+
23
+
24
+ class DecomposeAgent(BaseAgent):
25
+ """Topic decomposition Agent"""
26
+
27
+ def __init__(
28
+ self,
29
+ config: dict[str, Any],
30
+ api_key: str | None = None,
31
+ base_url: str | None = None,
32
+ api_version: str | None = None,
33
+ kb_name: str = "ai_textbook",
34
+ ):
35
+ language = config.get("system", {}).get("language", "zh")
36
+ super().__init__(
37
+ module_name="research",
38
+ agent_name="decompose_agent",
39
+ api_key=api_key,
40
+ base_url=base_url,
41
+ api_version=api_version,
42
+ language=language,
43
+ config=config,
44
+ )
45
+ # Load KB and RAG mode from config, no hardcoding
46
+ rag_cfg = config.get("rag", {})
47
+ self.kb_name = rag_cfg.get("kb_name", kb_name or "ai_textbook")
48
+ self.rag_mode = rag_cfg.get("default_mode", "hybrid")
49
+
50
+ # Check if RAG is enabled (from researching config)
51
+ researching_cfg = config.get("researching", {})
52
+ self.enable_rag = researching_cfg.get("enable_rag_hybrid", True) or researching_cfg.get(
53
+ "enable_rag_naive", True
54
+ )
55
+
56
+ # Citation manager (will be set during process)
57
+ self.citation_manager = None
58
+
59
+ def set_citation_manager(self, citation_manager):
60
+ """Set citation manager"""
61
+ self.citation_manager = citation_manager
62
+
63
+ async def process(
64
+ self, topic: str, num_subtopics: int = 5, mode: str = "manual"
65
+ ) -> dict[str, Any]:
66
+ """
67
+ Decompose topic into subtopics and generate overview for each subtopic
68
+
69
+ Args:
70
+ topic: Main topic
71
+ num_subtopics: Expected number of subtopics in manual mode, or maximum limit in auto mode
72
+ mode: Mode, "manual" (manually specify count) or "auto" (auto-generate)
73
+
74
+ Returns:
75
+ Dictionary containing decomposition results
76
+ {
77
+ "main_topic": str,
78
+ "sub_topics": [
79
+ {
80
+ "title": str,
81
+ "overview": str
82
+ },
83
+ ...
84
+ ],
85
+ "total_subtopics": int,
86
+ "mode": str
87
+ }
88
+ """
89
+ print(f"\n{'=' * 70}")
90
+ print("🔀 DecomposeAgent - Topic Decomposition")
91
+ print(f"{'=' * 70}")
92
+ print(f"Main Topic: {topic}")
93
+ print(f"Mode: {mode}")
94
+ print(f"RAG Enabled: {self.enable_rag}")
95
+ if mode == "auto":
96
+ print(f"Max Subtopic Limit: {num_subtopics}\n")
97
+ else:
98
+ print(f"Expected Subtopic Count: {num_subtopics}\n")
99
+
100
+ # If RAG is disabled, use direct LLM generation without RAG context
101
+ if not self.enable_rag:
102
+ print("⚠️ RAG is disabled, generating subtopics directly from LLM...")
103
+ return await self._process_without_rag(topic, num_subtopics, mode)
104
+
105
+ if mode == "auto":
106
+ # Auto mode: autonomously generate subtopics
107
+ return await self._process_auto_mode(topic, num_subtopics)
108
+ # Manual mode: generate based on specified count
109
+ return await self._process_manual_mode(topic, num_subtopics)
110
+
111
+ async def _process_without_rag(
112
+ self, topic: str, num_subtopics: int, mode: str = "manual"
113
+ ) -> dict[str, Any]:
114
+ """
115
+ Process without RAG: directly generate subtopics from LLM based on topic.
116
+ Used when RAG is disabled by user.
117
+
118
+ Args:
119
+ topic: Main topic
120
+ num_subtopics: Number of subtopics to generate (exact for manual, max for auto)
121
+ mode: "manual" or "auto"
122
+
123
+ Returns:
124
+ Dictionary containing decomposition results
125
+ """
126
+ print("\n🎯 Generating subtopics directly (no RAG)...")
127
+
128
+ system_prompt = self.get_prompt(
129
+ "system",
130
+ "role",
131
+ "You are a research planning expert. Your task is to decompose complex topics into clear subtopics.",
132
+ )
133
+
134
+ user_prompt_template = self.get_prompt("process", "decompose_without_rag")
135
+ if not user_prompt_template:
136
+ raise ValueError(
137
+ "DecomposeAgent missing decompose_without_rag prompt, please configure process.decompose_without_rag in prompts/{lang}/decompose_agent.yaml"
138
+ )
139
+
140
+ # Build requirement based on mode
141
+ if mode == "auto":
142
+ decompose_requirement = f"""
143
+ Quantity Requirements:
144
+ Generate between 3 and {num_subtopics} subtopics based on the complexity of the topic.
145
+ - For simple topics, generate fewer subtopics (3-4)
146
+ - For complex topics, generate more subtopics (up to {num_subtopics})
147
+ - Prioritize the most important and distinctive aspects of the topic
148
+ """
149
+ else:
150
+ decompose_requirement = f"""
151
+ Quantity Requirements:
152
+ Generate exactly {num_subtopics} subtopics. Please ensure exactly {num_subtopics} subtopics are generated, no more, no less.
153
+ """
154
+
155
+ user_prompt = user_prompt_template.format(
156
+ topic=topic, decompose_requirement=decompose_requirement
157
+ )
158
+
159
+ response = await self.call_llm(
160
+ user_prompt=user_prompt,
161
+ system_prompt=system_prompt,
162
+ stage="decompose_no_rag",
163
+ )
164
+
165
+ # Parse JSON output
166
+ from ..utils.json_utils import ensure_json_dict, ensure_keys, extract_json_from_text
167
+
168
+ data = extract_json_from_text(response)
169
+ try:
170
+ obj = ensure_json_dict(data)
171
+ ensure_keys(obj, ["sub_topics"])
172
+ subs = obj.get("sub_topics", [])
173
+ if not isinstance(subs, list):
174
+ raise ValueError("sub_topics must be an array")
175
+ # Clean and limit subtopics
176
+ cleaned = []
177
+ for it in subs[:num_subtopics]:
178
+ if isinstance(it, dict):
179
+ cleaned.append(
180
+ {"title": it.get("title", ""), "overview": it.get("overview", "")}
181
+ )
182
+ sub_topics = cleaned
183
+ except Exception:
184
+ sub_topics = []
185
+
186
+ print(f"✓ Generated {len(sub_topics)} subtopics (without RAG)")
187
+
188
+ return {
189
+ "main_topic": topic,
190
+ "sub_queries": [], # No sub-queries when RAG is disabled
191
+ "rag_context": "", # No RAG context
192
+ "sub_topics": sub_topics,
193
+ "total_subtopics": len(sub_topics),
194
+ "mode": f"{mode}_no_rag",
195
+ "rag_context_summary": "RAG disabled - subtopics generated directly from LLM",
196
+ }
197
+
198
+ async def _process_manual_mode(self, topic: str, num_subtopics: int) -> dict[str, Any]:
199
+ """Manual mode: generate subtopics based on specified count"""
200
+ # Step 1: Generate sub-queries
201
+ print("\n🔍 Step 1: Generating sub-queries...")
202
+ sub_queries = await self._generate_sub_queries(topic, num_subtopics)
203
+ print(f"✓ Generated {len(sub_queries)} sub-queries")
204
+
205
+ # Step 2: Execute RAG retrieval to get background knowledge
206
+ print("\n🔍 Step 2: Executing RAG retrieval...")
207
+ rag_contexts = {}
208
+ for i, query in enumerate(sub_queries, 1):
209
+ try:
210
+ result = await rag_search(query=query, kb_name=self.kb_name, mode=self.rag_mode)
211
+ rag_answer = result.get("answer", "")
212
+ rag_contexts[query] = rag_answer
213
+ print(f" ✓ Query {i}/{len(sub_queries)}: {query[:50]}...")
214
+
215
+ # Record citation (if citation manager is enabled)
216
+ if self.citation_manager:
217
+ # Get citation ID from CitationManager (unified ID generation)
218
+ citation_id = self.citation_manager.get_next_citation_id(stage="planning")
219
+ tool_type = f"rag_{self.rag_mode}" if self.rag_mode else "rag_hybrid"
220
+
221
+ # Create ToolTrace
222
+ import time
223
+
224
+ tool_id = f"plan_tool_{int(time.time() * 1000)}"
225
+ raw_answer_json = json.dumps(result, ensure_ascii=False)
226
+ trace = ToolTrace(
227
+ tool_id=tool_id,
228
+ citation_id=citation_id,
229
+ tool_type=tool_type,
230
+ query=query,
231
+ raw_answer=raw_answer_json,
232
+ summary=(
233
+ rag_answer[:500] if rag_answer else ""
234
+ ), # Use first 500 characters as summary
235
+ )
236
+
237
+ # Add to citation manager
238
+ self.citation_manager.add_citation(
239
+ citation_id=citation_id,
240
+ tool_type=tool_type,
241
+ tool_trace=trace,
242
+ raw_answer=raw_answer_json,
243
+ )
244
+ except Exception as e:
245
+ print(f" ✗ Query {i} failed: {e!s}")
246
+ rag_contexts[query] = ""
247
+
248
+ # Merge all RAG contexts
249
+ combined_rag_context = "\n\n".join(
250
+ [f"【{query}】\n{context}" for query, context in rag_contexts.items() if context]
251
+ )
252
+
253
+ # Step 3: Generate subtopics based on RAG background
254
+ print("\n🎯 Step 3: Generating subtopics...")
255
+ sub_topics = await self._generate_sub_topics(
256
+ topic=topic, rag_context=combined_rag_context, num_subtopics=num_subtopics
257
+ )
258
+
259
+ print(f"✓ Generated {len(sub_topics)} subtopics")
260
+
261
+ return {
262
+ "main_topic": topic,
263
+ "sub_queries": sub_queries,
264
+ "rag_context": combined_rag_context,
265
+ "sub_topics": sub_topics,
266
+ "total_subtopics": len(sub_topics),
267
+ "mode": "manual",
268
+ "rag_context_summary": f"Used RAG background from {len(rag_contexts)} queries",
269
+ }
270
+
271
+ async def _process_auto_mode(self, topic: str, max_subtopics: int) -> dict[str, Any]:
272
+ """Auto mode: autonomously generate subtopics based on topic and RAG context"""
273
+ # Step 1: First perform a broad RAG retrieval to get topic-related background knowledge
274
+ print("\n🔍 Step 1: Executing RAG retrieval to get background knowledge...")
275
+ try:
276
+ # Use topic itself as query to get related background
277
+ result = await rag_search(query=topic, kb_name=self.kb_name, mode=self.rag_mode)
278
+ rag_context = result.get("answer", "")
279
+ print(f" ✓ Retrieved background knowledge ({len(rag_context)} characters)")
280
+
281
+ # Record citation (if citation manager is enabled)
282
+ if self.citation_manager:
283
+ # Get citation ID from CitationManager (unified ID generation)
284
+ citation_id = self.citation_manager.get_next_citation_id(stage="planning")
285
+ tool_type = f"rag_{self.rag_mode}" if self.rag_mode else "rag_hybrid"
286
+
287
+ # Create ToolTrace
288
+ import time
289
+
290
+ tool_id = f"plan_tool_{int(time.time() * 1000)}"
291
+ raw_answer_json = json.dumps(result, ensure_ascii=False)
292
+ trace = ToolTrace(
293
+ tool_id=tool_id,
294
+ citation_id=citation_id,
295
+ tool_type=tool_type,
296
+ query=topic,
297
+ raw_answer=raw_answer_json,
298
+ summary=(
299
+ rag_context[:500] if rag_context else ""
300
+ ), # Use first 500 characters as summary
301
+ )
302
+
303
+ # Add to citation manager
304
+ self.citation_manager.add_citation(
305
+ citation_id=citation_id,
306
+ tool_type=tool_type,
307
+ tool_trace=trace,
308
+ raw_answer=raw_answer_json,
309
+ )
310
+ except Exception as e:
311
+ print(f" ✗ RAG retrieval failed: {e!s}")
312
+ rag_context = ""
313
+
314
+ # Step 2: Autonomously generate subtopics based on topic and RAG context
315
+ print("\n🎯 Step 2: Autonomously generating subtopics...")
316
+ sub_topics = await self._generate_sub_topics_auto(
317
+ topic=topic, rag_context=rag_context, max_subtopics=max_subtopics
318
+ )
319
+
320
+ print(f"✓ Autonomously generated {len(sub_topics)} subtopics")
321
+
322
+ return {
323
+ "main_topic": topic,
324
+ "sub_queries": [topic], # In auto mode, use topic itself as query
325
+ "rag_context": rag_context,
326
+ "sub_topics": sub_topics,
327
+ "total_subtopics": len(sub_topics),
328
+ "mode": "auto",
329
+ "rag_context_summary": "RAG background based on topic",
330
+ }
331
+
332
+ async def _generate_sub_topics_auto(
333
+ self, topic: str, rag_context: str, max_subtopics: int
334
+ ) -> list[dict[str, str]]:
335
+ """
336
+ Auto mode: Autonomously generate subtopics based on RAG background
337
+
338
+ Args:
339
+ topic: Main topic
340
+ rag_context: RAG background knowledge
341
+ max_subtopics: Maximum subtopic count limit
342
+
343
+ Returns:
344
+ Subtopics list
345
+ """
346
+ system_prompt = self.get_prompt("system", "role")
347
+ if not system_prompt:
348
+ raise ValueError(
349
+ "DecomposeAgent missing system prompt, please configure system.role in prompts/{lang}/decompose_agent.yaml"
350
+ )
351
+
352
+ user_prompt_template = self.get_prompt("process", "decompose")
353
+ if not user_prompt_template:
354
+ raise ValueError(
355
+ "DecomposeAgent missing decompose prompt, please configure process.decompose in prompts/{lang}/decompose_agent.yaml"
356
+ )
357
+
358
+ # Auto mode: Dynamically generate subtopics not exceeding the limit
359
+ decompose_requirement = f"""
360
+ Quantity Requirements:
361
+ Dynamically generate no more than {max_subtopics} subtopics. Please carefully analyze the background knowledge, identify core content areas related to the topic, and independently generate subtopics around the topic-related book content.
362
+ - The number of subtopics should be reasonable, not exceeding {max_subtopics}
363
+ - Prioritize subtopics most relevant and important to the topic
364
+ - Ensure subtopics do not duplicate and cover different dimensions of the topic
365
+ """
366
+
367
+ user_prompt = user_prompt_template.format(
368
+ topic=topic, rag_context=rag_context, decompose_requirement=decompose_requirement
369
+ )
370
+
371
+ response = await self.call_llm(
372
+ user_prompt=user_prompt, system_prompt=system_prompt, stage="decompose"
373
+ )
374
+
375
+ # Parse JSON output (strict validation)
376
+ from ..utils.json_utils import ensure_json_dict, ensure_keys, extract_json_from_text
377
+
378
+ data = extract_json_from_text(response)
379
+ try:
380
+ obj = ensure_json_dict(data)
381
+ ensure_keys(obj, ["sub_topics"])
382
+ subs = obj.get("sub_topics", [])
383
+ if not isinstance(subs, list):
384
+ raise ValueError("sub_topics must be an array")
385
+ # Limit count not exceeding max_subtopics
386
+ cleaned = []
387
+ for it in subs[:max_subtopics]:
388
+ if isinstance(it, dict):
389
+ cleaned.append(
390
+ {"title": it.get("title", ""), "overview": it.get("overview", "")}
391
+ )
392
+ return cleaned
393
+ except Exception:
394
+ # Fallback: return empty list
395
+ return []
396
+
397
+ async def _generate_sub_queries(self, topic: str, num_queries: int) -> list[str]:
398
+ """
399
+ Generate sub-queries
400
+
401
+ Args:
402
+ topic: Main topic
403
+ num_queries: Expected number of queries
404
+
405
+ Returns:
406
+ Query list
407
+ """
408
+ system_prompt = self.get_prompt("system", "role")
409
+ if not system_prompt:
410
+ raise ValueError(
411
+ "DecomposeAgent missing system prompt, please configure system.role in prompts/{lang}/decompose_agent.yaml"
412
+ )
413
+
414
+ user_prompt_template = self.get_prompt("process", "generate_queries")
415
+ if not user_prompt_template:
416
+ raise ValueError(
417
+ "DecomposeAgent missing generate_queries prompt, please configure process.generate_queries in prompts/{lang}/decompose_agent.yaml"
418
+ )
419
+
420
+ user_prompt = user_prompt_template.format(topic=topic, num_queries=num_queries)
421
+
422
+ response = await self.call_llm(
423
+ user_prompt=user_prompt,
424
+ system_prompt=system_prompt,
425
+ stage="generate_queries",
426
+ )
427
+
428
+ from ..utils.json_utils import ensure_json_dict, ensure_keys
429
+
430
+ data = extract_json_from_text(response)
431
+ try:
432
+ obj = ensure_json_dict(data)
433
+ ensure_keys(obj, ["queries"])
434
+ queries = obj.get("queries", [])
435
+ if not isinstance(queries, list):
436
+ raise ValueError("queries must be an array")
437
+ return queries[:num_queries]
438
+ except Exception:
439
+ # Fallback: extract queries from text
440
+ lines = response.split("\n")
441
+ queries = [line.strip() for line in lines if line.strip() and len(line.strip()) > 3]
442
+ return queries[:num_queries]
443
+
444
+ async def _generate_sub_topics(
445
+ self, topic: str, rag_context: str, num_subtopics: int
446
+ ) -> list[dict[str, str]]:
447
+ """
448
+ Generate subtopics based on RAG background
449
+
450
+ Args:
451
+ topic: Main topic
452
+ rag_context: RAG background knowledge
453
+ num_subtopics: Expected number of subtopics
454
+
455
+ Returns:
456
+ Subtopics list
457
+ """
458
+ system_prompt = self.get_prompt("system", "role")
459
+ if not system_prompt:
460
+ raise ValueError(
461
+ "DecomposeAgent missing system prompt, please configure system.role in prompts/{lang}/decompose_agent.yaml"
462
+ )
463
+
464
+ user_prompt_template = self.get_prompt("process", "decompose")
465
+ if not user_prompt_template:
466
+ raise ValueError(
467
+ "DecomposeAgent missing decompose prompt, please configure process.decompose in prompts/{lang}/decompose_agent.yaml"
468
+ )
469
+
470
+ # Manual mode: Explicitly generate specified number of subtopics
471
+ decompose_requirement = f"""
472
+ Quantity Requirements:
473
+ Explicitly generate {num_subtopics} subtopics. Please ensure exactly {num_subtopics} subtopics are generated, no more, no less.
474
+ """
475
+
476
+ user_prompt = user_prompt_template.format(
477
+ topic=topic, rag_context=rag_context, decompose_requirement=decompose_requirement
478
+ )
479
+
480
+ response = await self.call_llm(
481
+ user_prompt=user_prompt, system_prompt=system_prompt, stage="decompose"
482
+ )
483
+
484
+ # Parse JSON output (strict validation)
485
+ from ..utils.json_utils import ensure_json_dict, ensure_keys, extract_json_from_text
486
+
487
+ data = extract_json_from_text(response)
488
+ try:
489
+ obj = ensure_json_dict(data)
490
+ ensure_keys(obj, ["sub_topics"])
491
+ subs = obj.get("sub_topics", [])
492
+ if not isinstance(subs, list):
493
+ raise ValueError("sub_topics must be an array")
494
+ # Only select required fields
495
+ cleaned = []
496
+ for it in subs[:num_subtopics]:
497
+ if isinstance(it, dict):
498
+ cleaned.append(
499
+ {"title": it.get("title", ""), "overview": it.get("overview", "")}
500
+ )
501
+ return cleaned
502
+ except Exception:
503
+ # Fallback: return empty list
504
+ return []
505
+
506
+
507
+ __all__ = ["DecomposeAgent"]