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,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IdeaGen API Router
|
|
3
|
+
Used to generate research ideas from notebook content
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
# Ensure project modules can be imported
|
|
14
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
15
|
+
if str(project_root) not in sys.path:
|
|
16
|
+
sys.path.insert(0, str(project_root))
|
|
17
|
+
|
|
18
|
+
from src.agents.base_agent import BaseAgent
|
|
19
|
+
from src.agents.ideagen.idea_generation_workflow import IdeaGenerationWorkflow
|
|
20
|
+
from src.agents.ideagen.material_organizer_agent import MaterialOrganizerAgent
|
|
21
|
+
from src.api.utils.notebook_manager import NotebookManager
|
|
22
|
+
from src.api.utils.task_id_manager import TaskIDManager
|
|
23
|
+
from src.logging import get_logger
|
|
24
|
+
from src.services.config import load_config_with_main
|
|
25
|
+
from src.services.llm import get_llm_config
|
|
26
|
+
|
|
27
|
+
router = APIRouter()
|
|
28
|
+
|
|
29
|
+
# Initialize logger with config
|
|
30
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
31
|
+
config = load_config_with_main("solve_config.yaml", project_root) # Use any config to get main.yaml
|
|
32
|
+
log_dir = config.get("paths", {}).get("user_log_dir") or config.get("logging", {}).get("log_dir")
|
|
33
|
+
logger = get_logger("IdeaGen", level="INFO", log_dir=log_dir)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class IdeaGenRequest(BaseModel):
|
|
37
|
+
notebook_id: str
|
|
38
|
+
record_ids: list[str] | None = None # If None, use all records
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Define status constants to make state flow clearer
|
|
42
|
+
class IdeaGenStage:
|
|
43
|
+
"""IdeaGen status stages"""
|
|
44
|
+
|
|
45
|
+
INIT = "init" # Initialization
|
|
46
|
+
EXTRACTING = "extracting" # Extracting knowledge points
|
|
47
|
+
KNOWLEDGE_EXTRACTED = "knowledge_extracted" # Knowledge points extraction completed
|
|
48
|
+
FILTERING = "filtering" # Loose filtering
|
|
49
|
+
FILTERED = "filtered" # Filtering completed
|
|
50
|
+
EXPLORING = "exploring" # Exploring research ideas
|
|
51
|
+
EXPLORED = "explored" # Exploration completed
|
|
52
|
+
STRICT_FILTERING = "strict_filtering" # Strict filtering
|
|
53
|
+
GENERATING = "generating" # Generating statement
|
|
54
|
+
IDEA_READY = "idea_ready" # Single idea ready
|
|
55
|
+
COMPLETE = "complete" # All completed
|
|
56
|
+
ERROR = "error" # Error
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def send_status(
|
|
60
|
+
websocket: WebSocket, stage: str, message: str, data: dict = None, task_id: str = None
|
|
61
|
+
):
|
|
62
|
+
"""Unified status sending function"""
|
|
63
|
+
payload = {
|
|
64
|
+
"type": "status",
|
|
65
|
+
"stage": stage,
|
|
66
|
+
"message": message,
|
|
67
|
+
"timestamp": datetime.now().isoformat(),
|
|
68
|
+
}
|
|
69
|
+
if data:
|
|
70
|
+
payload["data"] = data
|
|
71
|
+
|
|
72
|
+
await websocket.send_json(payload)
|
|
73
|
+
|
|
74
|
+
# Log to file
|
|
75
|
+
log_msg = f"[{stage}] {message}"
|
|
76
|
+
if task_id:
|
|
77
|
+
log_msg = f"[{task_id}] {log_msg}"
|
|
78
|
+
logger.info(log_msg)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.websocket("/generate")
|
|
82
|
+
async def websocket_ideagen(websocket: WebSocket):
|
|
83
|
+
"""
|
|
84
|
+
WebSocket endpoint: Execute idea generation workflow
|
|
85
|
+
|
|
86
|
+
Status flow:
|
|
87
|
+
1. init -> Initialization
|
|
88
|
+
2. extracting -> Extract knowledge points
|
|
89
|
+
3. knowledge_extracted -> Knowledge points extraction completed
|
|
90
|
+
4. filtering -> Loose filtering
|
|
91
|
+
5. filtered -> Filtering completed
|
|
92
|
+
6. exploring -> Explore research ideas (loop)
|
|
93
|
+
7. explored -> Exploration completed
|
|
94
|
+
8. strict_filtering -> Strict filtering
|
|
95
|
+
9. generating -> Generate statement
|
|
96
|
+
10. idea_ready -> Single idea ready
|
|
97
|
+
11. complete -> All completed
|
|
98
|
+
|
|
99
|
+
Request format:
|
|
100
|
+
{
|
|
101
|
+
"notebook_id": "string", // Optional, single notebook mode
|
|
102
|
+
"record_ids": ["id1", "id2"], // Optional, specify specific records
|
|
103
|
+
"records": [...], // Optional, cross-notebook mode directly provide records
|
|
104
|
+
"user_thoughts": "string" // Optional, user additional thoughts
|
|
105
|
+
}
|
|
106
|
+
"""
|
|
107
|
+
await websocket.accept()
|
|
108
|
+
logger.info("=" * 60)
|
|
109
|
+
logger.info("WebSocket connection accepted")
|
|
110
|
+
logger.info("=" * 60)
|
|
111
|
+
|
|
112
|
+
# Get task ID manager
|
|
113
|
+
task_manager = TaskIDManager.get_instance()
|
|
114
|
+
task_id = None
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
# Receive request data
|
|
118
|
+
data = await websocket.receive_json()
|
|
119
|
+
notebook_id = data.get("notebook_id")
|
|
120
|
+
record_ids = data.get("record_ids")
|
|
121
|
+
direct_records = data.get("records")
|
|
122
|
+
user_thoughts = data.get("user_thoughts", "")
|
|
123
|
+
|
|
124
|
+
logger.info(
|
|
125
|
+
f"Received request: notebook_id={notebook_id}, record_ids={record_ids}, direct_records_count={len(direct_records) if direct_records else 0}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Generate task ID
|
|
129
|
+
task_key = (
|
|
130
|
+
f"ideagen_{notebook_id or 'cross_notebook'}_{hash(str(direct_records or record_ids))}"
|
|
131
|
+
)
|
|
132
|
+
task_id = task_manager.generate_task_id("ideagen", task_key)
|
|
133
|
+
|
|
134
|
+
# Send task ID to frontend
|
|
135
|
+
await websocket.send_json({"type": "task_id", "task_id": task_id})
|
|
136
|
+
logger.info(f"Task ID: {task_id}")
|
|
137
|
+
|
|
138
|
+
# ========== Stage 1: INIT ==========
|
|
139
|
+
await send_status(
|
|
140
|
+
websocket,
|
|
141
|
+
IdeaGenStage.INIT,
|
|
142
|
+
"Initializing idea generation workflow...",
|
|
143
|
+
task_id=task_id,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Reset LLM stats for this session
|
|
147
|
+
BaseAgent.reset_stats("ideagen")
|
|
148
|
+
|
|
149
|
+
# Get LLM configuration
|
|
150
|
+
llm_config = get_llm_config()
|
|
151
|
+
|
|
152
|
+
# Get records
|
|
153
|
+
records = []
|
|
154
|
+
|
|
155
|
+
if direct_records and isinstance(direct_records, list):
|
|
156
|
+
records = direct_records
|
|
157
|
+
logger.info(f"Using {len(records)} direct records")
|
|
158
|
+
elif notebook_id:
|
|
159
|
+
nb_manager = NotebookManager()
|
|
160
|
+
notebook = nb_manager.get_notebook(notebook_id)
|
|
161
|
+
if not notebook:
|
|
162
|
+
await send_status(
|
|
163
|
+
websocket, IdeaGenStage.ERROR, "Notebook not found", task_id=task_id
|
|
164
|
+
)
|
|
165
|
+
await websocket.close()
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
records = notebook.get("records", [])
|
|
169
|
+
if record_ids:
|
|
170
|
+
records = [r for r in records if r.get("id") in record_ids]
|
|
171
|
+
logger.info(f"Loaded {len(records)} records from notebook")
|
|
172
|
+
|
|
173
|
+
# Check if we have either records or user_thoughts
|
|
174
|
+
if not records and not user_thoughts:
|
|
175
|
+
await send_status(
|
|
176
|
+
websocket,
|
|
177
|
+
IdeaGenStage.ERROR,
|
|
178
|
+
"Please provide notebook records or describe your research topic",
|
|
179
|
+
task_id=task_id,
|
|
180
|
+
)
|
|
181
|
+
await websocket.close()
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# ========== Stage 2: EXTRACTING ==========
|
|
185
|
+
# If we have records, extract knowledge points from them
|
|
186
|
+
# If only user_thoughts, create a virtual knowledge point from the text
|
|
187
|
+
if records:
|
|
188
|
+
await send_status(
|
|
189
|
+
websocket,
|
|
190
|
+
IdeaGenStage.EXTRACTING,
|
|
191
|
+
f"Extracting knowledge points from {len(records)} records...",
|
|
192
|
+
{"record_count": len(records)},
|
|
193
|
+
task_id=task_id,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
organizer = MaterialOrganizerAgent(
|
|
197
|
+
api_key=llm_config.api_key,
|
|
198
|
+
base_url=llm_config.base_url,
|
|
199
|
+
api_version=getattr(llm_config, "api_version", None),
|
|
200
|
+
model=llm_config.model,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
knowledge_points = await organizer.process(
|
|
204
|
+
records, user_thoughts if user_thoughts else None
|
|
205
|
+
)
|
|
206
|
+
logger.info(f"Extracted {len(knowledge_points)} knowledge points")
|
|
207
|
+
else:
|
|
208
|
+
# Text-only mode: create virtual knowledge point from user_thoughts
|
|
209
|
+
await send_status(
|
|
210
|
+
websocket,
|
|
211
|
+
IdeaGenStage.EXTRACTING,
|
|
212
|
+
"Processing your research topic description...",
|
|
213
|
+
{"record_count": 0, "text_only_mode": True},
|
|
214
|
+
task_id=task_id,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Create a virtual knowledge point from user_thoughts
|
|
218
|
+
knowledge_points = [
|
|
219
|
+
{
|
|
220
|
+
"knowledge_point": "User Research Topic",
|
|
221
|
+
"description": user_thoughts.strip(),
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
logger.info("Created virtual knowledge point from user thoughts (text-only mode)")
|
|
225
|
+
|
|
226
|
+
# ========== Stage 3: KNOWLEDGE_EXTRACTED ==========
|
|
227
|
+
await send_status(
|
|
228
|
+
websocket,
|
|
229
|
+
IdeaGenStage.KNOWLEDGE_EXTRACTED,
|
|
230
|
+
f"Extracted {len(knowledge_points)} knowledge points",
|
|
231
|
+
{"knowledge_points": knowledge_points, "count": len(knowledge_points)},
|
|
232
|
+
task_id=task_id,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not knowledge_points:
|
|
236
|
+
await send_status(
|
|
237
|
+
websocket,
|
|
238
|
+
IdeaGenStage.COMPLETE,
|
|
239
|
+
"No valid knowledge points extracted from notes",
|
|
240
|
+
{"ideas": [], "count": 0},
|
|
241
|
+
task_id=task_id,
|
|
242
|
+
)
|
|
243
|
+
await websocket.close()
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
# ========== Stage 4: FILTERING (Loose Filter) ==========
|
|
247
|
+
await send_status(
|
|
248
|
+
websocket,
|
|
249
|
+
IdeaGenStage.FILTERING,
|
|
250
|
+
f"Filtering {len(knowledge_points)} knowledge points (loose criteria)...",
|
|
251
|
+
{"total": len(knowledge_points)},
|
|
252
|
+
task_id=task_id,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
workflow = IdeaGenerationWorkflow(
|
|
256
|
+
api_key=llm_config.api_key,
|
|
257
|
+
base_url=llm_config.base_url,
|
|
258
|
+
api_version=getattr(llm_config, "api_version", None),
|
|
259
|
+
model=llm_config.model,
|
|
260
|
+
progress_callback=None, # We manually manage status here
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
filtered_points = await workflow.loose_filter(knowledge_points)
|
|
264
|
+
logger.info(
|
|
265
|
+
f"Loose filter: {len(knowledge_points)} -> {len(filtered_points)} knowledge points"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# ========== Stage 5: FILTERED ==========
|
|
269
|
+
await send_status(
|
|
270
|
+
websocket,
|
|
271
|
+
IdeaGenStage.FILTERED,
|
|
272
|
+
f"Filtered to {len(filtered_points)} knowledge points",
|
|
273
|
+
{
|
|
274
|
+
"filtered_points": filtered_points,
|
|
275
|
+
"original": len(knowledge_points),
|
|
276
|
+
"filtered": len(filtered_points),
|
|
277
|
+
},
|
|
278
|
+
task_id=task_id,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if not filtered_points:
|
|
282
|
+
await send_status(
|
|
283
|
+
websocket,
|
|
284
|
+
IdeaGenStage.COMPLETE,
|
|
285
|
+
"All knowledge points were filtered out",
|
|
286
|
+
{"ideas": [], "count": 0},
|
|
287
|
+
task_id=task_id,
|
|
288
|
+
)
|
|
289
|
+
await websocket.close()
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# ========== Stage 6-10: Process each knowledge point ==========
|
|
293
|
+
all_ideas = []
|
|
294
|
+
total_points = len(filtered_points)
|
|
295
|
+
|
|
296
|
+
for idx, point in enumerate(filtered_points):
|
|
297
|
+
point_name = point.get("knowledge_point", f"Point {idx + 1}")
|
|
298
|
+
logger.info(f"Processing knowledge point {idx + 1}/{total_points}: {point_name}")
|
|
299
|
+
|
|
300
|
+
# ========== Stage 6: EXPLORING ==========
|
|
301
|
+
await send_status(
|
|
302
|
+
websocket,
|
|
303
|
+
IdeaGenStage.EXPLORING,
|
|
304
|
+
f"Exploring research ideas for: {point_name} ({idx + 1}/{total_points})",
|
|
305
|
+
{"index": idx + 1, "total": total_points, "knowledge_point": point_name},
|
|
306
|
+
task_id=task_id,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
research_ideas = await workflow.explore_ideas(point)
|
|
310
|
+
logger.info(f"Generated {len(research_ideas)} research ideas")
|
|
311
|
+
|
|
312
|
+
# ========== Stage 7: EXPLORED ==========
|
|
313
|
+
await send_status(
|
|
314
|
+
websocket,
|
|
315
|
+
IdeaGenStage.EXPLORED,
|
|
316
|
+
f"Generated {len(research_ideas)} research ideas for: {point_name}",
|
|
317
|
+
{
|
|
318
|
+
"index": idx + 1,
|
|
319
|
+
"ideas_count": len(research_ideas),
|
|
320
|
+
"knowledge_point": point_name,
|
|
321
|
+
},
|
|
322
|
+
task_id=task_id,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
if not research_ideas:
|
|
326
|
+
logger.warning("No ideas generated, skipping")
|
|
327
|
+
continue
|
|
328
|
+
|
|
329
|
+
# ========== Stage 8: STRICT_FILTERING ==========
|
|
330
|
+
await send_status(
|
|
331
|
+
websocket,
|
|
332
|
+
IdeaGenStage.STRICT_FILTERING,
|
|
333
|
+
f"Strictly filtering {len(research_ideas)} ideas for: {point_name}",
|
|
334
|
+
{
|
|
335
|
+
"index": idx + 1,
|
|
336
|
+
"ideas_count": len(research_ideas),
|
|
337
|
+
"knowledge_point": point_name,
|
|
338
|
+
},
|
|
339
|
+
task_id=task_id,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
kept_ideas = await workflow.strict_filter(point, research_ideas)
|
|
343
|
+
logger.info(f"Kept {len(kept_ideas)} ideas after strict filter")
|
|
344
|
+
|
|
345
|
+
if not kept_ideas:
|
|
346
|
+
logger.warning("No ideas kept, skipping")
|
|
347
|
+
continue
|
|
348
|
+
|
|
349
|
+
# ========== Stage 9: GENERATING ==========
|
|
350
|
+
await send_status(
|
|
351
|
+
websocket,
|
|
352
|
+
IdeaGenStage.GENERATING,
|
|
353
|
+
f"Generating statement for: {point_name}",
|
|
354
|
+
{"index": idx + 1, "kept_ideas": len(kept_ideas), "knowledge_point": point_name},
|
|
355
|
+
task_id=task_id,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
statement = await workflow.generate_statement(point, kept_ideas)
|
|
359
|
+
logger.info(f"Statement generated ({len(statement)} chars)")
|
|
360
|
+
|
|
361
|
+
idea_result = {
|
|
362
|
+
"id": f"idea-{idx}",
|
|
363
|
+
"knowledge_point": point_name,
|
|
364
|
+
"description": point.get("description", ""),
|
|
365
|
+
"research_ideas": kept_ideas,
|
|
366
|
+
"statement": statement,
|
|
367
|
+
"expanded": False,
|
|
368
|
+
}
|
|
369
|
+
all_ideas.append(idea_result)
|
|
370
|
+
|
|
371
|
+
# ========== Stage 10: IDEA_READY ==========
|
|
372
|
+
# Send status message
|
|
373
|
+
await send_status(
|
|
374
|
+
websocket,
|
|
375
|
+
IdeaGenStage.IDEA_READY,
|
|
376
|
+
f"Research idea ready: {point_name}",
|
|
377
|
+
{"index": idx + 1, "total": total_points},
|
|
378
|
+
task_id=task_id,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Important: Also send type="idea" message, frontend needs this to render ideas
|
|
382
|
+
await websocket.send_json({"type": "idea", "data": idea_result})
|
|
383
|
+
logger.info(f"Sent idea to frontend: {point_name}")
|
|
384
|
+
|
|
385
|
+
# ========== Stage 11: COMPLETE ==========
|
|
386
|
+
logger.success(
|
|
387
|
+
f"Workflow complete: generated {len(all_ideas)} ideas from {total_points} knowledge points"
|
|
388
|
+
)
|
|
389
|
+
await send_status(
|
|
390
|
+
websocket,
|
|
391
|
+
IdeaGenStage.COMPLETE,
|
|
392
|
+
f"Successfully generated {len(all_ideas)} research ideas",
|
|
393
|
+
{"ideas": all_ideas, "count": len(all_ideas)},
|
|
394
|
+
task_id=task_id,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Print LLM usage stats
|
|
398
|
+
BaseAgent.print_stats("ideagen")
|
|
399
|
+
|
|
400
|
+
# Update task status
|
|
401
|
+
task_manager.update_task_status(task_id, "completed")
|
|
402
|
+
logger.success(f"Task {task_id} completed")
|
|
403
|
+
|
|
404
|
+
except WebSocketDisconnect:
|
|
405
|
+
logger.info(f"WebSocket disconnected (task_id={task_id})")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
logger.error(f"ERROR: {e}")
|
|
408
|
+
|
|
409
|
+
logger.exception("Exception details:")
|
|
410
|
+
|
|
411
|
+
if task_id:
|
|
412
|
+
task_manager.update_task_status(task_id, "error", error=str(e))
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
await send_status(
|
|
416
|
+
websocket,
|
|
417
|
+
IdeaGenStage.ERROR,
|
|
418
|
+
f"Error: {e!s}",
|
|
419
|
+
{"error": str(e)},
|
|
420
|
+
task_id=task_id,
|
|
421
|
+
)
|
|
422
|
+
except (RuntimeError, WebSocketDisconnect, ConnectionError):
|
|
423
|
+
pass # Connection already closed
|
|
424
|
+
finally:
|
|
425
|
+
try:
|
|
426
|
+
await websocket.close()
|
|
427
|
+
logger.info("WebSocket closed")
|
|
428
|
+
except (RuntimeError, WebSocketDisconnect, ConnectionError):
|
|
429
|
+
pass # Connection already closed
|
|
430
|
+
logger.info("=" * 60)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@router.get("/test")
|
|
434
|
+
async def test_ideagen():
|
|
435
|
+
"""Test endpoint"""
|
|
436
|
+
return {"status": "ok", "message": "IdeaGen API is working"}
|