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.
- realtimex_deeptutor/__init__.py +67 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
- src/__init__.py +40 -0
- src/agents/__init__.py +24 -0
- src/agents/base_agent.py +657 -0
- src/agents/chat/__init__.py +24 -0
- src/agents/chat/chat_agent.py +435 -0
- src/agents/chat/prompts/en/chat_agent.yaml +35 -0
- src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
- src/agents/chat/session_manager.py +311 -0
- src/agents/co_writer/__init__.py +0 -0
- src/agents/co_writer/edit_agent.py +260 -0
- src/agents/co_writer/narrator_agent.py +423 -0
- src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
- src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
- src/agents/guide/__init__.py +16 -0
- src/agents/guide/agents/__init__.py +11 -0
- src/agents/guide/agents/chat_agent.py +104 -0
- src/agents/guide/agents/interactive_agent.py +223 -0
- src/agents/guide/agents/locate_agent.py +149 -0
- src/agents/guide/agents/summary_agent.py +150 -0
- src/agents/guide/guide_manager.py +500 -0
- src/agents/guide/prompts/en/chat_agent.yaml +41 -0
- src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
- src/agents/guide/prompts/en/locate_agent.yaml +68 -0
- src/agents/guide/prompts/en/summary_agent.yaml +157 -0
- src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
- src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
- src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
- src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
- src/agents/ideagen/__init__.py +12 -0
- src/agents/ideagen/idea_generation_workflow.py +426 -0
- src/agents/ideagen/material_organizer_agent.py +173 -0
- src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
- src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
- src/agents/question/__init__.py +24 -0
- src/agents/question/agents/__init__.py +18 -0
- src/agents/question/agents/generate_agent.py +381 -0
- src/agents/question/agents/relevance_analyzer.py +207 -0
- src/agents/question/agents/retrieve_agent.py +239 -0
- src/agents/question/coordinator.py +718 -0
- src/agents/question/example.py +109 -0
- src/agents/question/prompts/en/coordinator.yaml +75 -0
- src/agents/question/prompts/en/generate_agent.yaml +77 -0
- src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
- src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
- src/agents/question/prompts/zh/coordinator.yaml +75 -0
- src/agents/question/prompts/zh/generate_agent.yaml +77 -0
- src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
- src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
- src/agents/research/agents/__init__.py +23 -0
- src/agents/research/agents/decompose_agent.py +507 -0
- src/agents/research/agents/manager_agent.py +228 -0
- src/agents/research/agents/note_agent.py +180 -0
- src/agents/research/agents/rephrase_agent.py +263 -0
- src/agents/research/agents/reporting_agent.py +1333 -0
- src/agents/research/agents/research_agent.py +714 -0
- src/agents/research/data_structures.py +451 -0
- src/agents/research/main.py +188 -0
- src/agents/research/prompts/en/decompose_agent.yaml +89 -0
- src/agents/research/prompts/en/manager_agent.yaml +24 -0
- src/agents/research/prompts/en/note_agent.yaml +121 -0
- src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/en/reporting_agent.yaml +380 -0
- src/agents/research/prompts/en/research_agent.yaml +173 -0
- src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
- src/agents/research/prompts/zh/manager_agent.yaml +24 -0
- src/agents/research/prompts/zh/note_agent.yaml +121 -0
- src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
- src/agents/research/prompts/zh/research_agent.yaml +173 -0
- src/agents/research/research_pipeline.py +1309 -0
- src/agents/research/utils/__init__.py +60 -0
- src/agents/research/utils/citation_manager.py +799 -0
- src/agents/research/utils/json_utils.py +98 -0
- src/agents/research/utils/token_tracker.py +297 -0
- src/agents/solve/__init__.py +80 -0
- src/agents/solve/analysis_loop/__init__.py +14 -0
- src/agents/solve/analysis_loop/investigate_agent.py +414 -0
- src/agents/solve/analysis_loop/note_agent.py +190 -0
- src/agents/solve/main_solver.py +862 -0
- src/agents/solve/memory/__init__.py +34 -0
- src/agents/solve/memory/citation_memory.py +353 -0
- src/agents/solve/memory/investigate_memory.py +226 -0
- src/agents/solve/memory/solve_memory.py +340 -0
- src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
- src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
- src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
- src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
- src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
- src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
- src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
- src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
- src/agents/solve/solve_loop/__init__.py +22 -0
- src/agents/solve/solve_loop/citation_manager.py +74 -0
- src/agents/solve/solve_loop/manager_agent.py +274 -0
- src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
- src/agents/solve/solve_loop/response_agent.py +301 -0
- src/agents/solve/solve_loop/solve_agent.py +325 -0
- src/agents/solve/solve_loop/tool_agent.py +470 -0
- src/agents/solve/utils/__init__.py +64 -0
- src/agents/solve/utils/config_validator.py +313 -0
- src/agents/solve/utils/display_manager.py +223 -0
- src/agents/solve/utils/error_handler.py +363 -0
- src/agents/solve/utils/json_utils.py +98 -0
- src/agents/solve/utils/performance_monitor.py +407 -0
- src/agents/solve/utils/token_tracker.py +541 -0
- src/api/__init__.py +0 -0
- src/api/main.py +240 -0
- src/api/routers/__init__.py +1 -0
- src/api/routers/agent_config.py +69 -0
- src/api/routers/chat.py +296 -0
- src/api/routers/co_writer.py +337 -0
- src/api/routers/config.py +627 -0
- src/api/routers/dashboard.py +18 -0
- src/api/routers/guide.py +337 -0
- src/api/routers/ideagen.py +436 -0
- src/api/routers/knowledge.py +821 -0
- src/api/routers/notebook.py +247 -0
- src/api/routers/question.py +537 -0
- src/api/routers/research.py +394 -0
- src/api/routers/settings.py +164 -0
- src/api/routers/solve.py +305 -0
- src/api/routers/system.py +252 -0
- src/api/run_server.py +61 -0
- src/api/utils/history.py +172 -0
- src/api/utils/log_interceptor.py +21 -0
- src/api/utils/notebook_manager.py +415 -0
- src/api/utils/progress_broadcaster.py +72 -0
- src/api/utils/task_id_manager.py +100 -0
- src/config/__init__.py +0 -0
- src/config/accessors.py +18 -0
- src/config/constants.py +34 -0
- src/config/defaults.py +18 -0
- src/config/schema.py +38 -0
- src/config/settings.py +50 -0
- src/core/errors.py +62 -0
- src/knowledge/__init__.py +23 -0
- src/knowledge/add_documents.py +606 -0
- src/knowledge/config.py +65 -0
- src/knowledge/example_add_documents.py +236 -0
- src/knowledge/extract_numbered_items.py +1039 -0
- src/knowledge/initializer.py +621 -0
- src/knowledge/kb.py +22 -0
- src/knowledge/manager.py +782 -0
- src/knowledge/progress_tracker.py +182 -0
- src/knowledge/start_kb.py +535 -0
- src/logging/__init__.py +103 -0
- src/logging/adapters/__init__.py +17 -0
- src/logging/adapters/lightrag.py +184 -0
- src/logging/adapters/llamaindex.py +141 -0
- src/logging/config.py +80 -0
- src/logging/handlers/__init__.py +20 -0
- src/logging/handlers/console.py +75 -0
- src/logging/handlers/file.py +201 -0
- src/logging/handlers/websocket.py +127 -0
- src/logging/logger.py +709 -0
- src/logging/stats/__init__.py +16 -0
- src/logging/stats/llm_stats.py +179 -0
- src/services/__init__.py +56 -0
- src/services/config/__init__.py +61 -0
- src/services/config/knowledge_base_config.py +210 -0
- src/services/config/loader.py +260 -0
- src/services/config/unified_config.py +603 -0
- src/services/embedding/__init__.py +45 -0
- src/services/embedding/adapters/__init__.py +22 -0
- src/services/embedding/adapters/base.py +106 -0
- src/services/embedding/adapters/cohere.py +127 -0
- src/services/embedding/adapters/jina.py +99 -0
- src/services/embedding/adapters/ollama.py +116 -0
- src/services/embedding/adapters/openai_compatible.py +96 -0
- src/services/embedding/client.py +159 -0
- src/services/embedding/config.py +156 -0
- src/services/embedding/provider.py +119 -0
- src/services/llm/__init__.py +152 -0
- src/services/llm/capabilities.py +313 -0
- src/services/llm/client.py +302 -0
- src/services/llm/cloud_provider.py +530 -0
- src/services/llm/config.py +200 -0
- src/services/llm/error_mapping.py +103 -0
- src/services/llm/exceptions.py +152 -0
- src/services/llm/factory.py +450 -0
- src/services/llm/local_provider.py +347 -0
- src/services/llm/providers/anthropic.py +95 -0
- src/services/llm/providers/base_provider.py +93 -0
- src/services/llm/providers/open_ai.py +83 -0
- src/services/llm/registry.py +71 -0
- src/services/llm/telemetry.py +40 -0
- src/services/llm/types.py +27 -0
- src/services/llm/utils.py +333 -0
- src/services/prompt/__init__.py +25 -0
- src/services/prompt/manager.py +206 -0
- src/services/rag/__init__.py +64 -0
- src/services/rag/components/__init__.py +29 -0
- src/services/rag/components/base.py +59 -0
- src/services/rag/components/chunkers/__init__.py +18 -0
- src/services/rag/components/chunkers/base.py +34 -0
- src/services/rag/components/chunkers/fixed.py +71 -0
- src/services/rag/components/chunkers/numbered_item.py +94 -0
- src/services/rag/components/chunkers/semantic.py +97 -0
- src/services/rag/components/embedders/__init__.py +14 -0
- src/services/rag/components/embedders/base.py +32 -0
- src/services/rag/components/embedders/openai.py +63 -0
- src/services/rag/components/indexers/__init__.py +18 -0
- src/services/rag/components/indexers/base.py +35 -0
- src/services/rag/components/indexers/graph.py +172 -0
- src/services/rag/components/indexers/lightrag.py +156 -0
- src/services/rag/components/indexers/vector.py +146 -0
- src/services/rag/components/parsers/__init__.py +18 -0
- src/services/rag/components/parsers/base.py +35 -0
- src/services/rag/components/parsers/markdown.py +52 -0
- src/services/rag/components/parsers/pdf.py +115 -0
- src/services/rag/components/parsers/text.py +86 -0
- src/services/rag/components/retrievers/__init__.py +18 -0
- src/services/rag/components/retrievers/base.py +34 -0
- src/services/rag/components/retrievers/dense.py +200 -0
- src/services/rag/components/retrievers/hybrid.py +164 -0
- src/services/rag/components/retrievers/lightrag.py +169 -0
- src/services/rag/components/routing.py +286 -0
- src/services/rag/factory.py +234 -0
- src/services/rag/pipeline.py +215 -0
- src/services/rag/pipelines/__init__.py +32 -0
- src/services/rag/pipelines/academic.py +44 -0
- src/services/rag/pipelines/lightrag.py +43 -0
- src/services/rag/pipelines/llamaindex.py +313 -0
- src/services/rag/pipelines/raganything.py +384 -0
- src/services/rag/service.py +244 -0
- src/services/rag/types.py +73 -0
- src/services/search/__init__.py +284 -0
- src/services/search/base.py +87 -0
- src/services/search/consolidation.py +398 -0
- src/services/search/providers/__init__.py +128 -0
- src/services/search/providers/baidu.py +188 -0
- src/services/search/providers/exa.py +194 -0
- src/services/search/providers/jina.py +161 -0
- src/services/search/providers/perplexity.py +153 -0
- src/services/search/providers/serper.py +209 -0
- src/services/search/providers/tavily.py +161 -0
- src/services/search/types.py +114 -0
- src/services/setup/__init__.py +34 -0
- src/services/setup/init.py +285 -0
- src/services/tts/__init__.py +16 -0
- src/services/tts/config.py +99 -0
- src/tools/__init__.py +91 -0
- src/tools/code_executor.py +536 -0
- src/tools/paper_search_tool.py +171 -0
- src/tools/query_item_tool.py +310 -0
- src/tools/question/__init__.py +15 -0
- src/tools/question/exam_mimic.py +616 -0
- src/tools/question/pdf_parser.py +211 -0
- src/tools/question/question_extractor.py +397 -0
- src/tools/rag_tool.py +173 -0
- src/tools/tex_chunker.py +339 -0
- src/tools/tex_downloader.py +253 -0
- src/tools/web_search.py +71 -0
- src/utils/config_manager.py +206 -0
- src/utils/document_validator.py +168 -0
- src/utils/error_rate_tracker.py +111 -0
- src/utils/error_utils.py +82 -0
- src/utils/json_parser.py +110 -0
- 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"]
|