drizzlezyk commited on
Commit
4e15607
·
verified ·
1 Parent(s): 7da3441

Upload deepdiver_v2/cli/demo.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. deepdiver_v2/cli/demo.py +668 -0
deepdiver_v2/cli/demo.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2
+ #!/usr/bin/env python3
3
+ """
4
+ CLI Demo for DeepDiver Long Writer Multi-Agent System
5
+
6
+ This demo showcases the multi-agent system that includes:
7
+ - PlannerAgent: Coordinates and orchestrates the entire process
8
+ - InformationSeekerAgent: Gathers and researches information
9
+ - WriterAgent: Creates long-form content
10
+
11
+ Features:
12
+ - Loads configuration from config/.env file
13
+ - Shows real-time tool calls and reasoning traces
14
+ - Displays sub-agent responses and interactions
15
+ - Visualizes the complete execution flow
16
+ - Query preprocessing for safety and task suitability check
17
+ """
18
+
19
+ import os
20
+ import sys
21
+ import json
22
+ import time
23
+ import logging
24
+ import argparse
25
+ import requests
26
+ from pathlib import Path
27
+ from typing import Dict, Any, List, Optional
28
+ from rich.console import Console
29
+ from rich.table import Table
30
+ from rich.panel import Panel
31
+ from rich.syntax import Syntax
32
+ from rich.markdown import Markdown
33
+
34
+ # Add project root to Python path
35
+ project_root = Path(__file__).parent.parent
36
+ sys.path.insert(0, str(project_root))
37
+
38
+
39
+ # Configure logging to keep the CLI clean
40
+ def setup_clean_logging(debug_mode: bool = False):
41
+ """Configure logging to show only relevant information for the demo"""
42
+ if debug_mode:
43
+ # Debug mode: show all logs with timestamps
44
+ logging.basicConfig(
45
+ level=logging.DEBUG,
46
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
47
+ datefmt='%H:%M:%S'
48
+ )
49
+ else:
50
+ # Clean demo mode: suppress verbose logs
51
+
52
+ # Suppress specific noisy loggers
53
+ noisy_loggers = [
54
+ 'httpx',
55
+ 'httpcore',
56
+ 'urllib3',
57
+ 'src.tools.mcp_client',
58
+ # 'src.agents.base_agent',
59
+ 'config.config' # Also suppress config messages in quiet mode
60
+ ]
61
+
62
+ for logger_name in noisy_loggers:
63
+ logging.getLogger(logger_name).setLevel(logging.ERROR)
64
+
65
+ # Set up default clean logging before any imports
66
+ setup_clean_logging(debug_mode=False)
67
+
68
+ # Import the multi-agent system components
69
+ from config.config import get_config, reload_config
70
+ from src.agents.planner_agent import create_planner_agent
71
+ from src.agents.base_agent import AgentResponse
72
+
73
+ console = Console()
74
+
75
+
76
+ class DemoVisualizer:
77
+ """Visualizes the execution of the multi-agent system"""
78
+
79
+ def __init__(self, quiet_mode: bool = False):
80
+ self.console = console
81
+ self.execution_log = []
82
+ self.quiet_mode = quiet_mode
83
+
84
+ def _should_display(self, force: bool = False) -> bool:
85
+ """Check if output should be displayed based on quiet mode"""
86
+ return not self.quiet_mode or force
87
+
88
+ def show_welcome(self):
89
+ """Display welcome message and system info"""
90
+ if not self._should_display():
91
+ return
92
+
93
+ welcome_text = """
94
+ # 🤖 DeepDiver Long Writer Multi-Agent System Demo
95
+
96
+ This demo showcases an advanced multi-agent system for research and long-form content generation.
97
+
98
+ ## System Components:
99
+ - **🧠 PlannerAgent**: Orchestrates the entire process and coordinates sub-agents
100
+ - **🔍 InformationSeekerAgent**: Performs web research and gathers information
101
+ - **✍️ WriterAgent**: Creates comprehensive long-form content
102
+
103
+ ## Features:
104
+ - Real-time tool execution visualization
105
+ - Sub-agent response tracking
106
+ - Complete reasoning trace display
107
+ - Configuration management
108
+ - Query safety and suitability pre-check
109
+ """
110
+ self.console.print(Panel(Markdown(welcome_text), title="[bold blue]Welcome", border_style="blue"))
111
+
112
+ def show_config(self, config):
113
+ """Display current configuration"""
114
+ if not self._should_display():
115
+ return
116
+
117
+ config_table = Table(title="📋 System Configuration", show_header=True, header_style="bold magenta")
118
+ config_table.add_column("Setting", style="cyan", no_wrap=True)
119
+ config_table.add_column("Value", style="green")
120
+
121
+ # Safe config display (hide sensitive values)
122
+ safe_config = config.to_dict()
123
+ for key, value in safe_config.items():
124
+ if value is not None and str(value) != "None":
125
+ display_value = str(value)
126
+ if len(display_value) > 60:
127
+ display_value = display_value[:57] + "..."
128
+ config_table.add_row(key, display_value)
129
+
130
+ self.console.print(config_table)
131
+
132
+ def show_planner_start(self, query: str):
133
+ """Show planner starting execution"""
134
+ self.console.print(Panel(
135
+ f"[bold yellow]User Query:[/bold yellow] {query}\n\n"
136
+ f"[bold green]🚀 Starting PlannerAgent execution...[/bold green]",
137
+ title="[bold blue]Task Initiation",
138
+ border_style="green"
139
+ ))
140
+
141
+ def show_reasoning_step(self, iteration: int, reasoning: str):
142
+ """Display reasoning step"""
143
+ self.console.print(Panel(
144
+ Markdown(f"**Iteration {iteration} - Reasoning:**\n\n{reasoning}"),
145
+ title=f"[bold yellow]🧠 Agent Reasoning (Step {iteration})",
146
+ border_style="yellow"
147
+ ))
148
+
149
+ def show_tool_call(self, iteration: int, tool_name: str, arguments: Dict[str, Any]):
150
+ """Display tool call"""
151
+ args_json = json.dumps(arguments, indent=2, ensure_ascii=False)
152
+
153
+ self.console.print(Panel(
154
+ f"[bold cyan]Tool:[/bold cyan] {tool_name}\n\n"
155
+ f"[bold cyan]Arguments:[/bold cyan]\n{Syntax(args_json, 'json', theme='monokai', line_numbers=True)}",
156
+ title=f"[bold cyan]🔧 Tool Call (Step {iteration})",
157
+ border_style="cyan"
158
+ ))
159
+
160
+ def show_tool_result(self, iteration: int, tool_name: str, result: Dict[str, Any]):
161
+ """Display tool result"""
162
+ success = result.get("success", True)
163
+ status_icon = "✅" if success else "❌"
164
+ status_color = "green" if success else "red"
165
+
166
+ # Format result for display
167
+ if success and "data" in result:
168
+ display_result = result["data"]
169
+ elif "error" in result:
170
+ display_result = {"error": result["error"]}
171
+ else:
172
+ display_result = result
173
+
174
+ result_text = json.dumps(display_result, indent=2, ensure_ascii=False)
175
+ if len(result_text) > 1000:
176
+ result_text = result_text[:997] + "..."
177
+
178
+ self.console.print(Panel(
179
+ f"[bold {status_color}]Status:[/bold {status_color}] {status_icon} {'Success' if success else 'Failed'}\n\n"
180
+ f"[bold {status_color}]Result:[/bold {status_color}]\n{Syntax(result_text, 'json', theme='monokai', line_numbers=True)}",
181
+ title=f"[bold {status_color}]📋 Tool Result: {tool_name} (Step {iteration})",
182
+ border_style=status_color
183
+ ))
184
+
185
+ def show_sub_agent_execution(self, agent_name: str, task_content: str):
186
+ """Show sub-agent starting execution"""
187
+ self.console.print(Panel(
188
+ f"[bold magenta]Agent:[/bold magenta] {agent_name}\n\n"
189
+ f"[bold magenta]Task:[/bold magenta] {task_content[:500]}{'...' if len(task_content) > 500 else ''}",
190
+ title="[bold magenta]🤝 Sub-Agent Execution",
191
+ border_style="magenta"
192
+ ))
193
+
194
+ def show_sub_agent_result(self, agent_name: str, result: Dict[str, Any]):
195
+ """Show sub-agent execution result"""
196
+ success = result.get("success", True)
197
+ status_icon = "✅" if success else "❌"
198
+ status_color = "green" if success else "red"
199
+
200
+ # Extract key information
201
+ iterations = result.get("iterations", 0)
202
+ execution_time = result.get("execution_time", 0)
203
+
204
+ summary = f"[bold {status_color}]Status:[/bold {status_color}] {status_icon} {'Success' if success else 'Failed'}\n"
205
+ summary += f"[bold blue]Iterations:[/bold blue] {iterations}\n"
206
+ summary += f"[bold blue]Execution Time:[/bold blue] {execution_time:.2f}s\n\n"
207
+
208
+ if success and "data" in result:
209
+ data = result["data"]
210
+ if isinstance(data, dict):
211
+ for key, value in data.items():
212
+ if isinstance(value, str) and len(value) > 200:
213
+ summary += f"[bold blue]{key}:[/bold blue] {value[:197]}...\n"
214
+ else:
215
+ summary += f"[bold blue]{key}:[/bold blue] {value}\n"
216
+ elif "error" in result:
217
+ summary += f"[bold red]Error:[/bold red] {result['error']}\n"
218
+
219
+ self.console.print(Panel(
220
+ summary,
221
+ title=f"[bold {status_color}]📊 Sub-Agent Result: {agent_name}",
222
+ border_style=status_color
223
+ ))
224
+
225
+ def show_final_result(self, response: AgentResponse):
226
+ """Display final execution result"""
227
+ # Always show final results, even in quiet mode
228
+ if not self._should_display(force=True):
229
+ return
230
+
231
+ success = response.success
232
+ status_icon = "✅" if success else "❌"
233
+ status_color = "green" if success else "red"
234
+
235
+ summary = f"[bold {status_color}]Final Status:[/bold {status_color}] {status_icon} {'Completed Successfully' if success else 'Failed'}\n"
236
+ summary += f"[bold blue]Total Iterations:[/bold blue] {response.iterations}\n"
237
+ summary += f"[bold blue]Total Execution Time:[/bold blue] {response.execution_time:.2f}s\n"
238
+ summary += f"[bold blue]Agent:[/bold blue] {response.agent_name}\n\n"
239
+
240
+ if success and response.result:
241
+ if isinstance(response.result, dict):
242
+ for key, value in response.result.items():
243
+ if isinstance(value, str) and len(value) > 3000:
244
+ summary += f"[bold blue]{key}:[/bold blue] {value[:2997]}...\n\n"
245
+ else:
246
+ summary += f"[bold blue]{key}:[/bold blue] {value}\n\n"
247
+ elif response.error:
248
+ summary += f"[bold red]Error:[/bold red] {response.error}\n"
249
+
250
+ self.console.print(Panel(
251
+ summary,
252
+ title=f"[bold {status_color}]🏁 Final Result",
253
+ border_style=status_color
254
+ ))
255
+
256
+ def show_reasoning_trace(self, trace: List[Dict[str, Any]]):
257
+ """Display detailed reasoning trace"""
258
+ if not trace:
259
+ return
260
+
261
+ trace_table = Table(title="🔍 Detailed Execution Trace", show_header=True, header_style="bold cyan")
262
+ trace_table.add_column("Step", style="cyan", width=8)
263
+ trace_table.add_column("Type", style="magenta", width=12)
264
+ trace_table.add_column("Details", style="white")
265
+
266
+ for i, step in enumerate(trace, 1):
267
+ step_type = step.get("type", "unknown")
268
+
269
+ if step_type == "reasoning":
270
+ content = step.get("content", "")[:100] + ("..." if len(step.get("content", "")) > 100 else "")
271
+ trace_table.add_row(str(i), "🧠 Reasoning", content)
272
+
273
+ elif step_type == "action":
274
+ tool = step.get("tool", "")
275
+ result_status = "✅" if step.get("result", {}).get("success", True) else "❌"
276
+ trace_table.add_row(str(i), "🔧 Tool Call", f"{result_status} {tool}")
277
+
278
+ elif step_type == "error":
279
+ error = step.get("error", "")[:100] + ("..." if len(step.get("error", "")) > 100 else "")
280
+ trace_table.add_row(str(i), "❌ Error", error)
281
+
282
+ self.console.print(trace_table)
283
+
284
+ def show_unsupported_response(self):
285
+ """Display the fixed response for unsupported queries"""
286
+ # Always show this response, even in quiet mode
287
+ self.console.print(Panel(
288
+ "Sorry, your question is not within the current scope of tasks for DeepDiver-V2. Please try asking a question related to long-form writing or complex knowledge Q&A instead.",
289
+ title="[bold yellow]❌ Unsupported Query",
290
+ border_style="yellow"
291
+ ))
292
+
293
+
294
+ class AgentExecutionMonitor:
295
+ """Monitors agent execution and provides real-time feedback"""
296
+
297
+ def __init__(self, visualizer: DemoVisualizer):
298
+ self.visualizer = visualizer
299
+ self.current_iteration = 0
300
+
301
+ def on_reasoning_step(self, iteration: int, reasoning: str):
302
+ """Called when agent performs reasoning"""
303
+ self.visualizer.show_reasoning_step(iteration, reasoning)
304
+
305
+ def on_tool_call(self, iteration: int, tool_name: str, arguments: Dict[str, Any]):
306
+ """Called when agent makes a tool call"""
307
+ self.visualizer.show_tool_call(iteration, tool_name, arguments)
308
+
309
+ # Check for sub-agent assignments
310
+ if "assign_" in tool_name and "task" in tool_name:
311
+ if "tasks" in arguments:
312
+ for task in arguments.get("tasks", []):
313
+ task_content = task.get("task_content", "")
314
+ self.visualizer.show_sub_agent_execution("InformationSeeker", task_content)
315
+ elif "task_content" in arguments:
316
+ task_content = arguments.get("task_content", "")
317
+ self.visualizer.show_sub_agent_execution("Writer", task_content)
318
+
319
+ def on_tool_result(self, iteration: int, tool_name: str, result: Dict[str, Any]):
320
+ """Called when tool execution completes"""
321
+ self.visualizer.show_tool_result(iteration, tool_name, result)
322
+
323
+ # Show sub-agent results if this was an assignment
324
+ if "assign_" in tool_name and "task" in tool_name:
325
+ if "data" in result and "tasks" in result["data"]:
326
+ for task_result in result["data"]["tasks"]:
327
+ agent_name = task_result.get("agent_name", "InformationSeeker")
328
+ self.visualizer.show_sub_agent_result(agent_name, task_result)
329
+ elif "data" in result:
330
+ agent_name = result["data"].get("agent_name", "Writer")
331
+ self.visualizer.show_sub_agent_result(agent_name, result["data"])
332
+
333
+
334
+ def classify_query(query: str, config) -> Dict[str, Any]:
335
+ """
336
+ Classify user query into one of three categories using LLM:
337
+ 1. SAFE_SENSITIVE: Contains unsafe content (insults, political risks, etc.)
338
+ 2. NON_KNOWLEDGE: Non-knowledge intensive (no need for research, e.g., greetings, simple calculations)
339
+ 3. NORMAL: Requires processing (long-form writing or complex knowledge Q&A)
340
+
341
+ Returns:
342
+ Dict with 'category' (str) and 'reasoning' (str)
343
+ """
344
+ logger = logging.getLogger(__name__)
345
+
346
+ # Get model configuration
347
+ model_config = config.get_custom_llm_config()
348
+ pangu_url = model_config.get('url') or os.getenv('MODEL_REQUEST_URL', '')
349
+ model_token = model_config.get('token') or os.getenv('MODEL_REQUEST_TOKEN', '')
350
+
351
+ # Validate model configuration
352
+ if not pangu_url:
353
+ logger.error("Model URL not configured for query classification")
354
+ # Fallback to NORMAL category if model config is missing
355
+ return {
356
+ "category": "NORMAL",
357
+ "reasoning": "模型配置不完整,跳过分类检查,默认按正常任务处理"
358
+ }
359
+
360
+ headers = {'Content-Type': 'application/json', 'csb-token': model_token}
361
+
362
+ # Classification prompt (detailed instructions for accurate categorization)
363
+ prompt_template = """
364
+ 你是一个Query分类器,需要将用户输入的查询分为以下三类,并给出明确的分类理由:
365
+
366
+ 1. 【SAFE_SENSITIVE - 安全敏感内容】:包含以下任何一种情况的查询
367
+ - 辱骂、侮辱性语言(如脏话、人身攻击)
368
+ - 涉及政治敏感内容(如国家领导人、敏感政治事件、舆情风险话题)
369
+ - 违法违规内容(如暴力、色情、恐怖主义相关)
370
+ - 歧视性言论(种族、性别、宗教等歧视)
371
+
372
+ 2. 【NON_KNOWLEDGE - 非知识密集型任务】:不需要进行信息搜索的简单查询
373
+ - 问候语(如"你好"、"早上好"、"嗨")
374
+ - 简单计算(如"1+1等于几"、"25乘以4是多少")
375
+ - 基础闲聊(如"你是谁")
376
+ - 指令性语句(如"退出"、"帮助"、"开始")
377
+ - 不需要信息收集的简单问题
378
+
379
+ 3. 【NORMAL - 正常任务】:不包含安全敏感内容,需要进行信息搜索或长文写作的任务
380
+ - 简单的信息收集任务 (如"华为成立时间是什么时候")
381
+ - 复杂知识问答(如"ACL2025举办地有什么美食推荐")
382
+ - 长文写作任务(如"写一篇关于气候变化影响的5000字报告")
383
+ - 需要数据支持的分析(如"2023年全球经济增长数据及分析")
384
+ - 专业领域研究(如"机器学习在医疗诊断中的应用案例")
385
+
386
+ 分类要求:
387
+ - 严格按照上述定义进行分类,不要遗漏任何关键特征
388
+ - 优先判断是否为SAFE_SENSITIVE,其次判断是否为NON_KNOWLEDGE,最后才是NORMAL
389
+ - 必须提供清晰的分类理由,说明为什么属于该类别
390
+ - 输出格式必须严格遵循:先输出分类理由的思考,然后换行输出分类结果(SAFE_SENSITIVE/NON_KNOWLEDGE/NORMAL)
391
+
392
+ 示例1(SAFE_SENSITIVE):
393
+ 该查询包含辱骂性语言"XXX",符合安全敏感内容的定义,属于需要拦截的内容
394
+ SAFE_SENSITIVE
395
+
396
+ 示例2(NON_KNOWLEDGE):
397
+ 该查询是简单的问候语"你好",不需要进行信息搜索,属于非知识密集型任务
398
+ NON_KNOWLEDGE
399
+
400
+ 示例3(NORMAL):
401
+ 该查询不包含安全敏感内容,要求撰写关于"区块链技术在金融领域的应用"的长文,需要进行信息收集、案例研究和深度分析,属于正常的长文写作任务
402
+ NORMAL
403
+
404
+ 用户输入query:$query"""
405
+
406
+ # Prepare conversation history
407
+ conversation_history = [
408
+ {"role": "user", "content": prompt_template.replace("$query", query) + " /no_think"}
409
+ ]
410
+
411
+ try:
412
+ # Call LLM with retry logic
413
+ retry_num = 1
414
+ max_retry_num = 3
415
+ while retry_num <= max_retry_num:
416
+ try:
417
+ response = requests.post(
418
+ url=pangu_url,
419
+ headers=headers,
420
+ json={
421
+ "model": config.model_name,
422
+ "chat_template": "{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ '<s>[unused9]系统:[unused10]' }}{% endif %}{% if message['role'] == 'system' %}{{'<s>[unused9]系统:' + message['content'] + '[unused10]'}}{% endif %}{% if message['role'] == 'assistant' %}{{'[unused9]助手:' + message['content'] + '[unused10]'}}{% endif %}{% if message['role'] == 'tool' %}{{'[unused9]工具:' + message['content'] + '[unused10]'}}{% endif %}{% if message['role'] == 'function' %}{{'[unused9]方法:' + message['content'] + '[unused10]'}}{% endif %}{% if message['role'] == 'user' %}{{'[unused9]用户:' + message['content'] + '[unused10]'}}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ '[unused9]助手:' }}{% endif %}",
423
+ "spaces_between_special_tokens": False,
424
+ "messages": conversation_history,
425
+ "temperature": 0.1, # Low temperature for deterministic classification
426
+ "max_tokens": 5000,
427
+ },
428
+ timeout=model_config.get("timeout", 60)
429
+ )
430
+
431
+ response_json = response.json()
432
+ logger.debug(f"Classification API response: {json.dumps(response_json, indent=2)}")
433
+
434
+ # Extract and parse result
435
+ assistant_message = response_json["choices"][0]["message"]["content"].strip()
436
+ lines = assistant_message.split('\n', 1)
437
+
438
+ if len(lines) < 2:
439
+ raise ValueError(f"Invalid response format: {assistant_message}")
440
+
441
+ reasoning = lines[0].strip()
442
+ category = lines[1].strip() if len(lines) > 1 else "NORMAL"
443
+
444
+ # Validate category
445
+ valid_categories = ["SAFE_SENSITIVE", "NON_KNOWLEDGE", "NORMAL"]
446
+ if category not in valid_categories:
447
+ logger.warning(f"Invalid category '{category}', using fallback NORMAL")
448
+ category = "NORMAL"
449
+ reasoning = f"模型返回无效分类 '{category}',默认按正常任务处理。原始理由:{reasoning}"
450
+
451
+ return {
452
+ "category": category,
453
+ "reasoning": reasoning
454
+ }
455
+
456
+ except Exception as e:
457
+ logger.error(f"Classification attempt {retry_num} failed: {str(e)}")
458
+ if retry_num == max_retry_num:
459
+ raise
460
+ time.sleep(2) # Wait before retry
461
+ retry_num += 1
462
+
463
+ except Exception as e:
464
+ logger.error(f"Query classification failed: {str(e)}")
465
+ # Fallback to NORMAL category if classification fails
466
+ return {
467
+ "category": "NORMAL",
468
+ "reasoning": f"分类服务暂时不可用(错误:{str(e)[:100]}...),默认按正常任务处理"
469
+ }
470
+
471
+
472
+ def load_environment_config(quiet: bool = False):
473
+ """Load configuration from .env file"""
474
+ try:
475
+ # Check for .env file in config directory
476
+ config_dir = Path(__file__).parent.parent / "config"
477
+ env_file = config_dir / ".env"
478
+
479
+ if not env_file.exists():
480
+ if not quiet:
481
+ console.print(f"[yellow]⚠️ No .env file found at {env_file}[/yellow]")
482
+ console.print(f"[yellow]💡 Please copy env.template to config/.env and configure your settings[/yellow]")
483
+ return None
484
+
485
+ # Reload configuration to pick up .env file
486
+ reload_config()
487
+ config = get_config()
488
+
489
+ if not quiet:
490
+ console.print("[green]✅ Configuration loaded successfully[/green]")
491
+ return config
492
+
493
+ except Exception as e:
494
+ if not quiet:
495
+ console.print(f"[red]❌ Failed to load configuration: {e}[/red]")
496
+ return None
497
+
498
+
499
+ def create_sample_env_file():
500
+ """Create a sample .env file for demo purposes"""
501
+ config_dir = Path(__file__).parent.parent / "config"
502
+ env_file = config_dir / ".env"
503
+
504
+ if env_file.exists():
505
+ return
506
+
507
+ # Copy from template
508
+ template_file = Path(__file__).parent.parent / "env.template"
509
+ if template_file.exists():
510
+ import shutil
511
+ shutil.copy2(template_file, env_file)
512
+ console.print(f"[green]✅ Created .env file from template at {env_file}[/green]")
513
+ console.print("[yellow]⚠️ Please edit the .env file with your actual configuration values[/yellow]")
514
+ else:
515
+ console.print(f"[red]❌ Could not find env.template to copy[/red]")
516
+
517
+
518
+ def run_demo_query(planner, query: str, visualizer: DemoVisualizer, config) -> Optional[AgentResponse]:
519
+ """Run a demo query through the planner with preprocessing"""
520
+
521
+ # Step 1: Show query information
522
+ visualizer.show_planner_start(query)
523
+
524
+ # Step 2: Query classification (preprocessing)
525
+ classification_result = classify_query(query, config)
526
+
527
+ # Step 3: Branch processing based on classification
528
+ unsupported_categories = ["SAFE_SENSITIVE", "NON_KNOWLEDGE"]
529
+ if classification_result["category"] in unsupported_categories:
530
+ # Show fixed response for unsupported queries
531
+ visualizer.show_unsupported_response()
532
+ return None
533
+
534
+ # Step 4: Process normal query (original flow)
535
+ try:
536
+ # Execute the query
537
+ with console.status("[bold green]Executing planner task...", spinner="dots"):
538
+ response = planner.execute_task(query)
539
+
540
+ # Show final results
541
+ visualizer.show_final_result(response)
542
+
543
+ # Show detailed trace if available
544
+ if hasattr(response, 'reasoning_trace') and response.reasoning_trace:
545
+ visualizer.show_reasoning_trace(response.reasoning_trace)
546
+
547
+ return response
548
+
549
+ except Exception as e:
550
+ console.print(f"[red]❌ Error during execution: {e}[/red]")
551
+ return None
552
+
553
+
554
+ def main():
555
+ """Main CLI demo function"""
556
+ parser = argparse.ArgumentParser(description="DeepDiver Multi-Agent System Demo")
557
+ parser.add_argument("--query", "-q", type=str, help="Query to execute (interactive mode if not provided)")
558
+ parser.add_argument("--config-only", "-c", action="store_true", help="Only show configuration and exit")
559
+ parser.add_argument("--create-env", "-e", action="store_true", help="Create sample .env file from template")
560
+ parser.add_argument("--debug", "-d", action="store_true", help="Enable debug mode with verbose logging")
561
+ parser.add_argument("--quiet", help="Suppress all non-essential output")
562
+
563
+ args = parser.parse_args()
564
+
565
+ # Setup logging based on arguments (re-configure if debug mode is requested)
566
+ if args.debug:
567
+ setup_clean_logging(debug_mode=True)
568
+
569
+ # Initialize visualizer
570
+ visualizer = DemoVisualizer(quiet_mode=args.quiet)
571
+ if not args.quiet:
572
+ visualizer.show_welcome()
573
+
574
+ # Create sample .env file if requested
575
+ if args.create_env:
576
+ create_sample_env_file()
577
+ return 0
578
+
579
+ # Load configuration
580
+ config = load_environment_config(quiet=args.quiet)
581
+ if not config:
582
+ if not args.quiet:
583
+ console.print("[red]❌ Cannot proceed without valid configuration[/red]")
584
+ console.print("[yellow]💡 Use --create-env to create a sample configuration file[/yellow]")
585
+ return 1
586
+
587
+ # Show configuration
588
+ visualizer.show_config(config)
589
+
590
+ if args.config_only:
591
+ return 0
592
+
593
+ # Initialize planner agent
594
+ try:
595
+ if not args.quiet:
596
+ console.print("[blue]🔄 Initializing PlannerAgent...[/blue]")
597
+
598
+ # Create planner with sub-agent configurations
599
+ sub_agent_configs = {
600
+ "information_seeker": {
601
+ "model": config.model_name,
602
+ "max_iterations": config.information_seeker_max_iterations or 30,
603
+ },
604
+ "writer": {
605
+ "model": config.model_name,
606
+ "max_iterations": config.writer_max_iterations or 30,
607
+ "temperature": config.model_temperature,
608
+ "max_tokens": config.model_max_tokens
609
+ }
610
+ }
611
+
612
+ planner = create_planner_agent(
613
+ model=config.model_name,
614
+ max_iterations=config.planner_max_iterations or 40,
615
+ sub_agent_configs=sub_agent_configs
616
+ )
617
+
618
+ if not args.quiet:
619
+ console.print("[green]✅ PlannerAgent initialized successfully[/green]")
620
+
621
+ except Exception as e:
622
+ if not args.quiet:
623
+ console.print(f"[red]❌ Failed to initialize PlannerAgent: {e}[/red]")
624
+ return 1
625
+
626
+ # Handle query execution
627
+ if args.query:
628
+ # Single query mode
629
+ run_demo_query(planner, args.query, visualizer, config)
630
+ else:
631
+ # Interactive mode
632
+ if not args.quiet:
633
+ console.print("\n[bold blue]🎯 Interactive Mode[/bold blue]")
634
+ console.print("Enter your queries below. Type 'quit' or 'exit' to leave.")
635
+
636
+ while True:
637
+ try:
638
+ prompt_text = "\n[bold cyan]Enter your query:[/bold cyan] " if not args.quiet else "Query: "
639
+ query = console.input(prompt_text).strip()
640
+
641
+ if query.lower() in ['quit', 'exit', 'q']:
642
+ if not args.quiet:
643
+ console.print("[green]👋 Goodbye![/green]")
644
+ break
645
+
646
+ if not query:
647
+ continue
648
+
649
+ if not args.quiet:
650
+ console.print("\n" + "="*80 + "\n")
651
+ run_demo_query(planner, query, visualizer, config)
652
+ if not args.quiet:
653
+ console.print("\n" + "="*80 + "\n")
654
+
655
+ except KeyboardInterrupt:
656
+ if not args.quiet:
657
+ console.print("\n[yellow]⚠️ Interrupted by user[/yellow]")
658
+ break
659
+ except EOFError:
660
+ if not args.quiet:
661
+ console.print("\n[green]👋 Goodbye![/green]")
662
+ break
663
+
664
+ return 0
665
+
666
+
667
+ if __name__ == "__main__":
668
+ sys.exit(main())