jarvis-ai-assistant 0.3.26__py3-none-any.whl → 0.3.28__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +303 -177
- jarvis/jarvis_agent/agent_manager.py +6 -0
- jarvis/jarvis_agent/config.py +92 -0
- jarvis/jarvis_agent/config_editor.py +1 -1
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/file_methodology_manager.py +1 -3
- jarvis/jarvis_agent/jarvis.py +77 -36
- jarvis/jarvis_agent/memory_manager.py +70 -3
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/run_loop.py +130 -0
- jarvis/jarvis_agent/shell_input_handler.py +1 -1
- jarvis/jarvis_agent/task_analyzer.py +89 -11
- jarvis/jarvis_agent/task_manager.py +26 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_code_agent/code_agent.py +18 -3
- jarvis/jarvis_code_agent/lint.py +5 -5
- jarvis/jarvis_code_analysis/code_review.py +0 -1
- jarvis/jarvis_data/config_schema.json +7 -6
- jarvis/jarvis_git_squash/main.py +6 -1
- jarvis/jarvis_git_utils/git_commiter.py +51 -16
- jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
- jarvis/jarvis_memory_organizer/memory_organizer.py +2 -5
- jarvis/jarvis_methodology/main.py +0 -2
- jarvis/jarvis_multi_agent/__init__.py +3 -3
- jarvis/jarvis_platform/base.py +5 -6
- jarvis/jarvis_platform/registry.py +1 -1
- jarvis/jarvis_platform/yuanbao.py +0 -1
- jarvis/jarvis_platform_manager/main.py +28 -11
- jarvis/jarvis_platform_manager/service.py +1 -1
- jarvis/jarvis_rag/cli.py +1 -1
- jarvis/jarvis_rag/embedding_manager.py +0 -1
- jarvis/jarvis_rag/llm_interface.py +0 -3
- jarvis/jarvis_smart_shell/main.py +0 -1
- jarvis/jarvis_stats/cli.py +15 -35
- jarvis/jarvis_stats/stats.py +178 -51
- jarvis/jarvis_tools/clear_memory.py +1 -3
- jarvis/jarvis_tools/cli/main.py +0 -1
- jarvis/jarvis_tools/edit_file.py +0 -1
- jarvis/jarvis_tools/generate_new_tool.py +3 -5
- jarvis/jarvis_tools/registry.py +17 -3
- jarvis/jarvis_tools/retrieve_memory.py +2 -3
- jarvis/jarvis_tools/save_memory.py +3 -3
- jarvis/jarvis_tools/search_web.py +2 -2
- jarvis/jarvis_tools/sub_agent.py +114 -85
- jarvis/jarvis_tools/sub_code_agent.py +29 -7
- jarvis/jarvis_tools/virtual_tty.py +3 -14
- jarvis/jarvis_utils/builtin_replace_map.py +4 -4
- jarvis/jarvis_utils/config.py +44 -15
- jarvis/jarvis_utils/fzf.py +56 -0
- jarvis/jarvis_utils/git_utils.py +1 -1
- jarvis/jarvis_utils/globals.py +1 -2
- jarvis/jarvis_utils/input.py +0 -3
- jarvis/jarvis_utils/methodology.py +3 -5
- jarvis/jarvis_utils/output.py +1 -1
- jarvis/jarvis_utils/utils.py +117 -27
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/METADATA +2 -3
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/RECORD +62 -56
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/__init__.py
CHANGED
@@ -3,10 +3,16 @@
|
|
3
3
|
import datetime
|
4
4
|
import os
|
5
5
|
import platform
|
6
|
+
import re
|
6
7
|
from pathlib import Path
|
8
|
+
from enum import Enum
|
7
9
|
from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
|
8
10
|
|
9
11
|
# 第三方库导入
|
12
|
+
from rich.align import Align
|
13
|
+
from rich.console import Console
|
14
|
+
from rich.panel import Panel
|
15
|
+
from rich.text import Text
|
10
16
|
|
11
17
|
# 本地库导入
|
12
18
|
# jarvis_agent 相关
|
@@ -23,6 +29,13 @@ from jarvis.jarvis_agent.prompts import (
|
|
23
29
|
SUMMARY_REQUEST_PROMPT,
|
24
30
|
TASK_ANALYSIS_PROMPT,
|
25
31
|
)
|
32
|
+
from jarvis.jarvis_tools.registry import ToolRegistry
|
33
|
+
from jarvis.jarvis_agent.prompt_manager import PromptManager
|
34
|
+
from jarvis.jarvis_agent.event_bus import EventBus
|
35
|
+
from jarvis.jarvis_agent.config import AgentConfig
|
36
|
+
from jarvis.jarvis_agent.run_loop import AgentRunLoop
|
37
|
+
from jarvis.jarvis_agent.user_interaction import UserInteractionHandler
|
38
|
+
from jarvis.jarvis_utils.methodology import _load_all_methodologies
|
26
39
|
|
27
40
|
# jarvis_platform 相关
|
28
41
|
from jarvis.jarvis_platform.base import BasePlatform
|
@@ -54,7 +67,10 @@ from jarvis.jarvis_utils.tag import ot
|
|
54
67
|
|
55
68
|
|
56
69
|
def show_agent_startup_stats(
|
57
|
-
agent_name: str,
|
70
|
+
agent_name: str,
|
71
|
+
model_name: str,
|
72
|
+
tool_registry_instance: Optional[Any] = None,
|
73
|
+
platform_name: Optional[str] = None,
|
58
74
|
) -> None:
|
59
75
|
"""输出启动时的统计信息
|
60
76
|
|
@@ -63,16 +79,6 @@ def show_agent_startup_stats(
|
|
63
79
|
model_name: 使用的模型名称
|
64
80
|
"""
|
65
81
|
try:
|
66
|
-
from jarvis.jarvis_utils.methodology import _load_all_methodologies
|
67
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
68
|
-
from jarvis.jarvis_utils.config import get_data_dir
|
69
|
-
from pathlib import Path
|
70
|
-
from rich.console import Console
|
71
|
-
from rich.panel import Panel
|
72
|
-
from rich.text import Text
|
73
|
-
from rich.align import Align
|
74
|
-
import os
|
75
|
-
|
76
82
|
methodologies = _load_all_methodologies()
|
77
83
|
methodology_count = len(methodologies)
|
78
84
|
|
@@ -109,7 +115,8 @@ def show_agent_startup_stats(
|
|
109
115
|
current_dir = os.getcwd()
|
110
116
|
|
111
117
|
# 构建欢迎信息
|
112
|
-
|
118
|
+
platform = platform_name or get_normal_platform_name()
|
119
|
+
welcome_message = f"{agent_name} 初始化完成 - 使用 {platform} 平台 {model_name} 模型"
|
113
120
|
|
114
121
|
stats_parts = [
|
115
122
|
f"📚 本地方法论: [bold cyan]{methodology_count}[/bold cyan]",
|
@@ -198,12 +205,23 @@ origin_agent_system_prompt = f"""
|
|
198
205
|
"""
|
199
206
|
|
200
207
|
|
208
|
+
class LoopAction(Enum):
|
209
|
+
SKIP_TURN = "skip_turn"
|
210
|
+
CONTINUE = "continue"
|
211
|
+
COMPLETE = "complete"
|
212
|
+
|
213
|
+
|
201
214
|
class Agent:
|
202
215
|
def clear_history(self):
|
203
216
|
"""
|
204
217
|
Clears the current conversation history by delegating to the session manager.
|
205
218
|
"""
|
206
219
|
self.session.clear_history()
|
220
|
+
# 广播清理历史后的事件
|
221
|
+
try:
|
222
|
+
self.event_bus.emit("after_history_clear", agent=self)
|
223
|
+
except Exception:
|
224
|
+
pass
|
207
225
|
|
208
226
|
def __del__(self):
|
209
227
|
# 只有在记录启动时才停止记录
|
@@ -279,15 +297,16 @@ class Agent:
|
|
279
297
|
self._init_session()
|
280
298
|
|
281
299
|
# 初始化处理器
|
282
|
-
safe_output_handlers: List[OutputHandlerProtocol] = []
|
283
|
-
if output_handler:
|
284
|
-
safe_output_handlers = output_handler
|
285
|
-
safe_use_tools: List[str] = []
|
286
|
-
if use_tools:
|
287
|
-
safe_use_tools = use_tools
|
288
300
|
self._init_handlers(
|
289
|
-
|
301
|
+
output_handler or [],
|
302
|
+
input_handler,
|
303
|
+
multiline_inputer,
|
304
|
+
use_tools or [],
|
290
305
|
)
|
306
|
+
# 初始化用户交互封装,保持向后兼容
|
307
|
+
self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.user_confirm)
|
308
|
+
# 将确认函数指向封装后的 confirm,保持既有调用不变
|
309
|
+
self.user_confirm = self.user_interaction.confirm # type: ignore[assignment]
|
291
310
|
|
292
311
|
# 初始化配置
|
293
312
|
self._init_config(
|
@@ -299,16 +318,24 @@ class Agent:
|
|
299
318
|
force_save_memory,
|
300
319
|
)
|
301
320
|
|
321
|
+
# 初始化事件总线需先于管理器,以便管理器在构造中安全订阅事件
|
322
|
+
self.event_bus = EventBus()
|
302
323
|
# 初始化管理器
|
303
324
|
self.memory_manager = MemoryManager(self)
|
304
325
|
self.task_analyzer = TaskAnalyzer(self)
|
305
326
|
self.file_methodology_manager = FileMethodologyManager(self)
|
327
|
+
self.prompt_manager = PromptManager(self)
|
306
328
|
|
307
329
|
# 设置系统提示词
|
308
330
|
self._setup_system_prompt()
|
309
331
|
|
310
332
|
# 输出统计信息(包含欢迎信息)
|
311
|
-
show_agent_startup_stats(
|
333
|
+
show_agent_startup_stats(
|
334
|
+
name,
|
335
|
+
self.model.name(),
|
336
|
+
self.get_tool_registry(), # type: ignore
|
337
|
+
platform_name=self.model.platform_name(), # type: ignore
|
338
|
+
)
|
312
339
|
|
313
340
|
def _init_model(self, model_group: Optional[str]):
|
314
341
|
"""初始化模型平台(统一使用 normal 平台/模型)"""
|
@@ -340,16 +367,10 @@ class Agent:
|
|
340
367
|
use_tools: List[str],
|
341
368
|
):
|
342
369
|
"""初始化各种处理器"""
|
343
|
-
|
344
|
-
|
345
|
-
self.output_handler = output_handler if output_handler else [ToolRegistry()]
|
370
|
+
self.output_handler = output_handler or [ToolRegistry()]
|
346
371
|
self.set_use_tools(use_tools)
|
347
|
-
|
348
|
-
self.
|
349
|
-
|
350
|
-
self.multiline_inputer = (
|
351
|
-
multiline_inputer if multiline_inputer else get_multiline_input
|
352
|
-
)
|
372
|
+
self.input_handler = input_handler or []
|
373
|
+
self.multiline_inputer = multiline_inputer or get_multiline_input
|
353
374
|
|
354
375
|
def _init_config(
|
355
376
|
self,
|
@@ -361,47 +382,57 @@ class Agent:
|
|
361
382
|
force_save_memory: Optional[bool],
|
362
383
|
):
|
363
384
|
"""初始化配置选项"""
|
364
|
-
#
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
self.
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
self.
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
if force_save_memory is not None
|
392
|
-
else is_force_save_memory()
|
393
|
-
)
|
385
|
+
# 使用集中配置解析,保持与原逻辑一致
|
386
|
+
cfg = AgentConfig(
|
387
|
+
system_prompt=self.system_prompt,
|
388
|
+
name=self.name,
|
389
|
+
description=self.description,
|
390
|
+
model_group=model_group,
|
391
|
+
auto_complete=self.auto_complete,
|
392
|
+
need_summary=self.need_summary,
|
393
|
+
summary_prompt=summary_prompt,
|
394
|
+
execute_tool_confirm=execute_tool_confirm,
|
395
|
+
use_methodology=use_methodology,
|
396
|
+
use_analysis=use_analysis,
|
397
|
+
force_save_memory=force_save_memory,
|
398
|
+
files=self.files,
|
399
|
+
max_token_count=None,
|
400
|
+
).resolve_defaults()
|
401
|
+
|
402
|
+
# 将解析结果回填到 Agent 实例属性,保持向后兼容
|
403
|
+
self.use_methodology = bool(cfg.use_methodology)
|
404
|
+
self.use_analysis = bool(cfg.use_analysis)
|
405
|
+
self.execute_tool_confirm = bool(cfg.execute_tool_confirm)
|
406
|
+
self.summary_prompt = cfg.summary_prompt or DEFAULT_SUMMARY_PROMPT
|
407
|
+
self.max_token_count = int(cfg.max_token_count or get_max_token_count(model_group))
|
408
|
+
self.force_save_memory = bool(cfg.force_save_memory)
|
409
|
+
|
410
|
+
# 聚合配置到 AgentConfig,作为后续单一事实来源(保持兼容,不改变既有属性使用)
|
411
|
+
self.config = cfg
|
394
412
|
|
395
413
|
def _setup_system_prompt(self):
|
396
414
|
"""设置系统提示词"""
|
397
|
-
|
398
|
-
|
399
|
-
|
415
|
+
try:
|
416
|
+
if hasattr(self, "prompt_manager"):
|
417
|
+
prompt_text = self.prompt_manager.build_system_prompt()
|
418
|
+
else:
|
419
|
+
action_prompt = self.get_tool_usage_prompt()
|
420
|
+
prompt_text = f"""
|
400
421
|
{self.system_prompt}
|
401
422
|
|
402
423
|
{action_prompt}
|
403
424
|
"""
|
404
|
-
|
425
|
+
self.model.set_system_prompt(prompt_text) # type: ignore
|
426
|
+
except Exception:
|
427
|
+
# 回退到原始行为,确保兼容性
|
428
|
+
action_prompt = self.get_tool_usage_prompt()
|
429
|
+
self.model.set_system_prompt( # type: ignore
|
430
|
+
f"""
|
431
|
+
{self.system_prompt}
|
432
|
+
|
433
|
+
{action_prompt}
|
434
|
+
"""
|
435
|
+
)
|
405
436
|
|
406
437
|
def set_user_data(self, key: str, value: Any):
|
407
438
|
"""Sets user data in the session."""
|
@@ -413,8 +444,6 @@ class Agent:
|
|
413
444
|
|
414
445
|
def set_use_tools(self, use_tools):
|
415
446
|
"""设置要使用的工具列表"""
|
416
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
417
|
-
|
418
447
|
for handler in self.output_handler:
|
419
448
|
if isinstance(handler, ToolRegistry):
|
420
449
|
if use_tools:
|
@@ -435,6 +464,9 @@ class Agent:
|
|
435
464
|
If the configured multiline_inputer supports 'print_on_empty' keyword, pass it;
|
436
465
|
otherwise, fall back to calling with a single argument for compatibility.
|
437
466
|
"""
|
467
|
+
# 优先通过用户交互封装,便于未来替换 UI
|
468
|
+
if hasattr(self, "user_interaction"):
|
469
|
+
return self.user_interaction.multiline_input(tip, print_on_empty)
|
438
470
|
try:
|
439
471
|
# Try to pass the keyword for enhanced input handler
|
440
472
|
return self.multiline_inputer(tip, print_on_empty=print_on_empty) # type: ignore
|
@@ -463,13 +495,15 @@ class Agent:
|
|
463
495
|
|
464
496
|
def get_tool_registry(self) -> Optional[Any]:
|
465
497
|
"""获取工具注册表实例"""
|
466
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
467
|
-
|
468
498
|
for handler in self.output_handler:
|
469
499
|
if isinstance(handler, ToolRegistry):
|
470
500
|
return handler
|
471
501
|
return None
|
472
502
|
|
503
|
+
def get_event_bus(self) -> EventBus:
|
504
|
+
"""获取事件总线实例"""
|
505
|
+
return self.event_bus
|
506
|
+
|
473
507
|
def _call_model(
|
474
508
|
self, message: str, need_complete: bool = False, run_input_handlers: bool = True
|
475
509
|
) -> str:
|
@@ -518,11 +552,38 @@ class Agent:
|
|
518
552
|
|
519
553
|
def _add_addon_prompt(self, message: str, need_complete: bool) -> str:
|
520
554
|
"""添加附加提示到消息"""
|
555
|
+
# 广播添加附加提示前事件(不影响主流程)
|
556
|
+
try:
|
557
|
+
self.event_bus.emit(
|
558
|
+
"before_addon_prompt",
|
559
|
+
agent=self,
|
560
|
+
need_complete=need_complete,
|
561
|
+
current_message=message,
|
562
|
+
has_session_addon=bool(self.session.addon_prompt),
|
563
|
+
)
|
564
|
+
except Exception:
|
565
|
+
pass
|
566
|
+
|
567
|
+
addon_text = ""
|
521
568
|
if self.session.addon_prompt:
|
522
|
-
|
569
|
+
addon_text = self.session.addon_prompt
|
570
|
+
message += f"\n\n{addon_text}"
|
523
571
|
self.session.addon_prompt = ""
|
524
572
|
else:
|
525
|
-
|
573
|
+
addon_text = self.make_default_addon_prompt(need_complete)
|
574
|
+
message += f"\n\n{addon_text}"
|
575
|
+
|
576
|
+
# 广播添加附加提示后事件(不影响主流程)
|
577
|
+
try:
|
578
|
+
self.event_bus.emit(
|
579
|
+
"after_addon_prompt",
|
580
|
+
agent=self,
|
581
|
+
need_complete=need_complete,
|
582
|
+
addon_text=addon_text,
|
583
|
+
final_message=message,
|
584
|
+
)
|
585
|
+
except Exception:
|
586
|
+
pass
|
526
587
|
return message
|
527
588
|
|
528
589
|
def _manage_conversation_length(self, message: str) -> str:
|
@@ -542,7 +603,29 @@ class Agent:
|
|
542
603
|
if not self.model:
|
543
604
|
raise RuntimeError("Model not initialized")
|
544
605
|
|
606
|
+
# 事件:模型调用前
|
607
|
+
try:
|
608
|
+
self.event_bus.emit(
|
609
|
+
"before_model_call",
|
610
|
+
agent=self,
|
611
|
+
message=message,
|
612
|
+
)
|
613
|
+
except Exception:
|
614
|
+
pass
|
615
|
+
|
545
616
|
response = self.model.chat_until_success(message) # type: ignore
|
617
|
+
|
618
|
+
# 事件:模型调用后
|
619
|
+
try:
|
620
|
+
self.event_bus.emit(
|
621
|
+
"after_model_call",
|
622
|
+
agent=self,
|
623
|
+
message=message,
|
624
|
+
response=response,
|
625
|
+
)
|
626
|
+
except Exception:
|
627
|
+
pass
|
628
|
+
|
546
629
|
self.session.conversation_length += get_context_token_count(response)
|
547
630
|
|
548
631
|
return response
|
@@ -565,7 +648,7 @@ class Agent:
|
|
565
648
|
) # type: ignore
|
566
649
|
|
567
650
|
return summary
|
568
|
-
except Exception
|
651
|
+
except Exception:
|
569
652
|
PrettyOutput.print("总结对话历史失败", OutputType.ERROR)
|
570
653
|
return ""
|
571
654
|
|
@@ -586,12 +669,11 @@ class Agent:
|
|
586
669
|
注意:
|
587
670
|
当上下文长度超过最大值时使用
|
588
671
|
"""
|
589
|
-
#
|
672
|
+
# 在清理历史之前,提示用户保存重要记忆(事件驱动触发实际保存)
|
590
673
|
if self.force_save_memory:
|
591
674
|
PrettyOutput.print(
|
592
675
|
"对话历史即将被总结和清理,请先保存重要信息...", OutputType.INFO
|
593
676
|
)
|
594
|
-
self.memory_manager.prompt_memory_save()
|
595
677
|
|
596
678
|
if self._should_use_file_upload():
|
597
679
|
return self._handle_history_with_file_upload()
|
@@ -613,17 +695,38 @@ class Agent:
|
|
613
695
|
|
614
696
|
# 清理历史(但不清理prompt,因为prompt会在builtin_input_handler中设置)
|
615
697
|
if self.model:
|
698
|
+
# 广播清理历史前事件
|
699
|
+
try:
|
700
|
+
self.event_bus.emit("before_history_clear", agent=self)
|
701
|
+
except Exception:
|
702
|
+
pass
|
616
703
|
self.model.reset()
|
617
704
|
# 重置后重新设置系统提示词,确保系统约束仍然生效
|
618
705
|
self._setup_system_prompt()
|
619
706
|
# 重置会话
|
620
707
|
self.session.clear_history()
|
708
|
+
# 广播清理历史后的事件
|
709
|
+
try:
|
710
|
+
self.event_bus.emit("after_history_clear", agent=self)
|
711
|
+
except Exception:
|
712
|
+
pass
|
621
713
|
|
622
714
|
return formatted_summary
|
623
715
|
|
624
716
|
def _handle_history_with_file_upload(self) -> str:
|
625
717
|
"""使用文件上传方式处理历史"""
|
626
|
-
|
718
|
+
# 广播清理历史前事件
|
719
|
+
try:
|
720
|
+
self.event_bus.emit("before_history_clear", agent=self)
|
721
|
+
except Exception:
|
722
|
+
pass
|
723
|
+
result = self.file_methodology_manager.handle_history_with_file_upload()
|
724
|
+
# 广播清理历史后的事件
|
725
|
+
try:
|
726
|
+
self.event_bus.emit("after_history_clear", agent=self)
|
727
|
+
except Exception:
|
728
|
+
pass
|
729
|
+
return result
|
627
730
|
|
628
731
|
def _format_summary_message(self, summary: str) -> str:
|
629
732
|
"""格式化摘要消息"""
|
@@ -654,30 +757,56 @@ class Agent:
|
|
654
757
|
2. 对于子Agent: 可能会生成总结(如果启用)
|
655
758
|
3. 使用spinner显示生成状态
|
656
759
|
"""
|
657
|
-
#
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
if self.use_analysis:
|
663
|
-
self.task_analyzer.analysis_task(satisfaction_feedback)
|
664
|
-
|
665
|
-
# 当开启强制保存记忆时,在分析步骤之后触发一次记忆保存
|
666
|
-
if self.force_save_memory:
|
667
|
-
self.memory_manager.prompt_memory_save()
|
668
|
-
|
760
|
+
# 事件驱动方式:
|
761
|
+
# - TaskAnalyzer 通过订阅 before_summary/task_completed 事件执行分析与满意度收集
|
762
|
+
# - MemoryManager 通过订阅 before_history_clear/task_completed 事件执行记忆保存(受 force_save_memory 控制)
|
763
|
+
# 为减少耦合,这里不再直接调用上述组件,保持行为由事件触发
|
669
764
|
self._check_and_organize_memory()
|
670
765
|
|
766
|
+
result = "任务完成"
|
767
|
+
|
671
768
|
if self.need_summary:
|
672
769
|
|
673
770
|
self.session.prompt = self.summary_prompt
|
771
|
+
# 广播将要生成总结事件
|
772
|
+
try:
|
773
|
+
self.event_bus.emit(
|
774
|
+
"before_summary",
|
775
|
+
agent=self,
|
776
|
+
prompt=self.session.prompt,
|
777
|
+
auto_completed=auto_completed,
|
778
|
+
need_summary=self.need_summary,
|
779
|
+
)
|
780
|
+
except Exception:
|
781
|
+
pass
|
782
|
+
|
674
783
|
if not self.model:
|
675
784
|
raise RuntimeError("Model not initialized")
|
676
785
|
ret = self.model.chat_until_success(self.session.prompt) # type: ignore
|
786
|
+
result = ret
|
787
|
+
|
788
|
+
# 广播完成总结事件
|
789
|
+
try:
|
790
|
+
self.event_bus.emit(
|
791
|
+
"after_summary",
|
792
|
+
agent=self,
|
793
|
+
summary=result,
|
794
|
+
)
|
795
|
+
except Exception:
|
796
|
+
pass
|
677
797
|
|
678
|
-
|
798
|
+
# 广播任务完成事件(不影响主流程)
|
799
|
+
try:
|
800
|
+
self.event_bus.emit(
|
801
|
+
"task_completed",
|
802
|
+
agent=self,
|
803
|
+
auto_completed=auto_completed,
|
804
|
+
need_summary=self.need_summary,
|
805
|
+
)
|
806
|
+
except Exception:
|
807
|
+
pass
|
679
808
|
|
680
|
-
return
|
809
|
+
return result
|
681
810
|
|
682
811
|
def make_default_addon_prompt(self, need_complete: bool) -> str:
|
683
812
|
"""生成附加提示。
|
@@ -686,6 +815,10 @@ class Agent:
|
|
686
815
|
need_complete: 是否需要完成任务
|
687
816
|
|
688
817
|
"""
|
818
|
+
# 优先使用 PromptManager 以保持逻辑集中
|
819
|
+
if hasattr(self, "prompt_manager"):
|
820
|
+
return self.prompt_manager.build_default_addon_prompt(need_complete)
|
821
|
+
|
689
822
|
# 结构化系统指令
|
690
823
|
action_handlers = ", ".join([handler.name() for handler in self.output_handler])
|
691
824
|
|
@@ -737,6 +870,17 @@ class Agent:
|
|
737
870
|
self.session.prompt = f"{user_input}"
|
738
871
|
try:
|
739
872
|
set_agent(self.name, self)
|
873
|
+
# 广播任务开始事件(不影响主流程)
|
874
|
+
try:
|
875
|
+
self.event_bus.emit(
|
876
|
+
"task_started",
|
877
|
+
agent=self,
|
878
|
+
name=self.name,
|
879
|
+
description=self.description,
|
880
|
+
user_input=self.session.prompt,
|
881
|
+
)
|
882
|
+
except Exception:
|
883
|
+
pass
|
740
884
|
return self._main_loop()
|
741
885
|
except Exception as e:
|
742
886
|
PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
|
@@ -744,81 +888,17 @@ class Agent:
|
|
744
888
|
|
745
889
|
def _main_loop(self) -> Any:
|
746
890
|
"""主运行循环"""
|
747
|
-
|
891
|
+
# 委派至独立的运行循环类,保持行为一致
|
892
|
+
loop = AgentRunLoop(self)
|
893
|
+
return loop.run()
|
748
894
|
|
749
|
-
|
750
|
-
try:
|
751
|
-
# 更新输入处理器标志
|
752
|
-
if self.run_input_handlers_next_turn:
|
753
|
-
run_input_handlers = True
|
754
|
-
self.run_input_handlers_next_turn = False
|
755
|
-
|
756
|
-
# 首次运行初始化
|
757
|
-
if self.first:
|
758
|
-
self._first_run()
|
759
|
-
|
760
|
-
# 调用模型获取响应
|
761
|
-
current_response = self._call_model(
|
762
|
-
self.session.prompt, True, run_input_handlers
|
763
|
-
)
|
764
|
-
|
765
|
-
self.session.prompt = ""
|
766
|
-
run_input_handlers = False
|
767
|
-
|
768
|
-
# 处理中断
|
769
|
-
interrupt_result = self._handle_run_interrupt(current_response)
|
770
|
-
if interrupt_result:
|
771
|
-
if isinstance(interrupt_result, tuple):
|
772
|
-
run_input_handlers, should_continue = interrupt_result
|
773
|
-
if should_continue:
|
774
|
-
self.run_input_handlers_next_turn = True
|
775
|
-
continue
|
776
|
-
else:
|
777
|
-
return interrupt_result
|
778
|
-
|
779
|
-
# 处理工具调用
|
780
|
-
need_return, prompt = self._call_tools(current_response)
|
781
|
-
if self.session.prompt and prompt:
|
782
|
-
self.session.prompt += "\n\n" + prompt
|
783
|
-
else:
|
784
|
-
self.session.prompt = prompt
|
785
|
-
|
786
|
-
if need_return:
|
787
|
-
return self.session.prompt
|
788
|
-
|
789
|
-
# 执行回调
|
790
|
-
if self.after_tool_call_cb:
|
791
|
-
self.after_tool_call_cb(self)
|
792
|
-
|
793
|
-
# 检查是否需要继续
|
794
|
-
if self.session.prompt or self.session.addon_prompt:
|
795
|
-
continue
|
796
|
-
|
797
|
-
# 检查自动完成
|
798
|
-
if self.auto_complete and ot("!!!COMPLETE!!!") in current_response:
|
799
|
-
return self._complete_task(auto_completed=True)
|
800
|
-
|
801
|
-
# 获取下一步用户输入
|
802
|
-
next_action = self._get_next_user_action()
|
803
|
-
if next_action == "continue":
|
804
|
-
run_input_handlers = True
|
805
|
-
continue
|
806
|
-
elif next_action == "complete":
|
807
|
-
return self._complete_task(auto_completed=False)
|
808
|
-
|
809
|
-
except Exception as e:
|
810
|
-
PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
|
811
|
-
return f"Task failed: {str(e)}"
|
812
|
-
|
813
|
-
def _handle_run_interrupt(
|
814
|
-
self, current_response: str
|
815
|
-
) -> Optional[Union[Any, Tuple[bool, bool]]]:
|
895
|
+
def _handle_run_interrupt(self, current_response: str) -> Optional[Union[Any, "LoopAction"]]:
|
816
896
|
"""处理运行中的中断
|
817
897
|
|
818
898
|
返回:
|
819
|
-
None:
|
820
|
-
Any:
|
821
|
-
|
899
|
+
None: 无中断,或中断后允许继续执行当前响应
|
900
|
+
Any: 需要返回的最终结果
|
901
|
+
LoopAction.SKIP_TURN: 中断后需要跳过当前响应,并立即开始下一次循环
|
822
902
|
"""
|
823
903
|
if not get_interrupt():
|
824
904
|
return None
|
@@ -827,6 +907,16 @@ class Agent:
|
|
827
907
|
user_input = self._multiline_input(
|
828
908
|
"模型交互期间被中断,请输入用户干预信息:", False
|
829
909
|
)
|
910
|
+
# 广播中断事件(包含用户输入,可能为空字符串)
|
911
|
+
try:
|
912
|
+
self.event_bus.emit(
|
913
|
+
"interrupt_triggered",
|
914
|
+
agent=self,
|
915
|
+
current_response=current_response,
|
916
|
+
user_input=user_input,
|
917
|
+
)
|
918
|
+
except Exception:
|
919
|
+
pass
|
830
920
|
|
831
921
|
self.run_input_handlers_next_turn = True
|
832
922
|
|
@@ -840,16 +930,16 @@ class Agent:
|
|
840
930
|
return None # 继续执行工具调用
|
841
931
|
else:
|
842
932
|
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
843
|
-
return
|
933
|
+
return LoopAction.SKIP_TURN # 请求主循环 continue
|
844
934
|
else:
|
845
935
|
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}"
|
846
|
-
return
|
936
|
+
return LoopAction.SKIP_TURN # 请求主循环 continue
|
847
937
|
|
848
|
-
def _get_next_user_action(self) -> str:
|
938
|
+
def _get_next_user_action(self) -> Union[str, "LoopAction"]:
|
849
939
|
"""获取用户下一步操作
|
850
940
|
|
851
941
|
返回:
|
852
|
-
|
942
|
+
LoopAction.CONTINUE 或 LoopAction.COMPLETE(兼容旧字符串值 "continue"/"complete")
|
853
943
|
"""
|
854
944
|
user_input = self._multiline_input(
|
855
945
|
f"{self.name}: 请输入,或输入空行来结束当前任务:", False
|
@@ -857,9 +947,10 @@ class Agent:
|
|
857
947
|
|
858
948
|
if user_input:
|
859
949
|
self.session.prompt = user_input
|
860
|
-
|
950
|
+
# 使用显式动作信号,保留返回类型注释以保持兼容
|
951
|
+
return LoopAction.CONTINUE # type: ignore[return-value]
|
861
952
|
else:
|
862
|
-
return
|
953
|
+
return LoopAction.COMPLETE # type: ignore[return-value]
|
863
954
|
|
864
955
|
def _first_run(self):
|
865
956
|
"""首次运行初始化"""
|
@@ -879,11 +970,20 @@ class Agent:
|
|
879
970
|
|
880
971
|
self.first = False
|
881
972
|
|
973
|
+
def _create_temp_model(self, system_prompt: str) -> BasePlatform:
|
974
|
+
"""创建一个用于执行一次性任务的临时模型实例,以避免污染主会话。"""
|
975
|
+
temp_model = PlatformRegistry().create_platform(
|
976
|
+
self.model.platform_name() # type: ignore
|
977
|
+
)
|
978
|
+
if not temp_model:
|
979
|
+
raise RuntimeError("创建临时模型失败。")
|
980
|
+
|
981
|
+
temp_model.set_model_name(self.model.name()) # type: ignore
|
982
|
+
temp_model.set_system_prompt(system_prompt)
|
983
|
+
return temp_model
|
984
|
+
|
882
985
|
def _filter_tools_if_needed(self, task: str):
|
883
986
|
"""如果工具数量超过阈值,使用大模型筛选相关工具"""
|
884
|
-
import re
|
885
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
886
|
-
|
887
987
|
tool_registry = self.get_tool_registry()
|
888
988
|
if not isinstance(tool_registry, ToolRegistry):
|
889
989
|
return
|
@@ -917,19 +1017,21 @@ class Agent:
|
|
917
1017
|
PrettyOutput.print(
|
918
1018
|
f"工具数量超过{threshold}个,正在使用AI筛选相关工具...", OutputType.INFO
|
919
1019
|
)
|
920
|
-
|
921
|
-
# 使用临时模型实例调用模型,以避免污染历史记录
|
1020
|
+
# 广播工具筛选开始事件
|
922
1021
|
try:
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
1022
|
+
self.event_bus.emit(
|
1023
|
+
"before_tool_filter",
|
1024
|
+
agent=self,
|
1025
|
+
task=task,
|
1026
|
+
total_tools=len(all_tools),
|
1027
|
+
threshold=threshold,
|
927
1028
|
)
|
928
|
-
|
929
|
-
|
1029
|
+
except Exception:
|
1030
|
+
pass
|
930
1031
|
|
931
|
-
|
932
|
-
|
1032
|
+
# 使用临时模型实例调用模型,以避免污染历史记录
|
1033
|
+
try:
|
1034
|
+
temp_model = self._create_temp_model("你是一个帮助筛选工具的助手。")
|
933
1035
|
selected_tools_str = temp_model.chat_until_success(
|
934
1036
|
selection_prompt
|
935
1037
|
) # type: ignore
|
@@ -954,10 +1056,34 @@ class Agent:
|
|
954
1056
|
f"已筛选出 {len(selected_tool_names)} 个相关工具: {', '.join(selected_tool_names)}",
|
955
1057
|
OutputType.SUCCESS,
|
956
1058
|
)
|
1059
|
+
# 广播工具筛选事件
|
1060
|
+
try:
|
1061
|
+
self.event_bus.emit(
|
1062
|
+
"tool_filtered",
|
1063
|
+
agent=self,
|
1064
|
+
task=task,
|
1065
|
+
selected_tools=selected_tool_names,
|
1066
|
+
total_tools=len(all_tools),
|
1067
|
+
threshold=threshold,
|
1068
|
+
)
|
1069
|
+
except Exception:
|
1070
|
+
pass
|
957
1071
|
else:
|
958
1072
|
PrettyOutput.print(
|
959
1073
|
"AI 未能筛选出任何相关工具,将使用所有工具。", OutputType.WARNING
|
960
1074
|
)
|
1075
|
+
# 广播工具筛选事件(无筛选结果)
|
1076
|
+
try:
|
1077
|
+
self.event_bus.emit(
|
1078
|
+
"tool_filtered",
|
1079
|
+
agent=self,
|
1080
|
+
task=task,
|
1081
|
+
selected_tools=[],
|
1082
|
+
total_tools=len(all_tools),
|
1083
|
+
threshold=threshold,
|
1084
|
+
)
|
1085
|
+
except Exception:
|
1086
|
+
pass
|
961
1087
|
|
962
1088
|
except Exception as e:
|
963
1089
|
PrettyOutput.print(
|