jarvis-ai-assistant 0.3.26__py3-none-any.whl → 0.3.27__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 +290 -173
- jarvis/jarvis_agent/config.py +92 -0
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/jarvis.py +69 -35
- jarvis/jarvis_agent/memory_manager.py +70 -2
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/run_loop.py +130 -0
- jarvis/jarvis_agent/task_analyzer.py +88 -9
- 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_data/config_schema.json +7 -6
- jarvis/jarvis_git_squash/main.py +6 -1
- jarvis/jarvis_git_utils/git_commiter.py +38 -12
- jarvis/jarvis_platform/base.py +4 -5
- jarvis/jarvis_platform_manager/main.py +28 -11
- jarvis/jarvis_stats/cli.py +13 -32
- jarvis/jarvis_stats/stats.py +179 -51
- jarvis/jarvis_tools/registry.py +15 -0
- jarvis/jarvis_tools/sub_agent.py +94 -84
- jarvis/jarvis_tools/sub_code_agent.py +12 -6
- jarvis/jarvis_utils/config.py +14 -0
- jarvis/jarvis_utils/fzf.py +56 -0
- jarvis/jarvis_utils/input.py +0 -3
- jarvis/jarvis_utils/utils.py +56 -8
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/METADATA +2 -3
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/RECORD +33 -27
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.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
|
@@ -63,16 +76,6 @@ def show_agent_startup_stats(
|
|
63
76
|
model_name: 使用的模型名称
|
64
77
|
"""
|
65
78
|
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
79
|
methodologies = _load_all_methodologies()
|
77
80
|
methodology_count = len(methodologies)
|
78
81
|
|
@@ -198,12 +201,23 @@ origin_agent_system_prompt = f"""
|
|
198
201
|
"""
|
199
202
|
|
200
203
|
|
204
|
+
class LoopAction(Enum):
|
205
|
+
SKIP_TURN = "skip_turn"
|
206
|
+
CONTINUE = "continue"
|
207
|
+
COMPLETE = "complete"
|
208
|
+
|
209
|
+
|
201
210
|
class Agent:
|
202
211
|
def clear_history(self):
|
203
212
|
"""
|
204
213
|
Clears the current conversation history by delegating to the session manager.
|
205
214
|
"""
|
206
215
|
self.session.clear_history()
|
216
|
+
# 广播清理历史后的事件
|
217
|
+
try:
|
218
|
+
self.event_bus.emit("after_history_clear", agent=self)
|
219
|
+
except Exception:
|
220
|
+
pass
|
207
221
|
|
208
222
|
def __del__(self):
|
209
223
|
# 只有在记录启动时才停止记录
|
@@ -279,15 +293,16 @@ class Agent:
|
|
279
293
|
self._init_session()
|
280
294
|
|
281
295
|
# 初始化处理器
|
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
296
|
self._init_handlers(
|
289
|
-
|
297
|
+
output_handler or [],
|
298
|
+
input_handler,
|
299
|
+
multiline_inputer,
|
300
|
+
use_tools or [],
|
290
301
|
)
|
302
|
+
# 初始化用户交互封装,保持向后兼容
|
303
|
+
self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.user_confirm)
|
304
|
+
# 将确认函数指向封装后的 confirm,保持既有调用不变
|
305
|
+
self.user_confirm = self.user_interaction.confirm # type: ignore[assignment]
|
291
306
|
|
292
307
|
# 初始化配置
|
293
308
|
self._init_config(
|
@@ -299,10 +314,13 @@ class Agent:
|
|
299
314
|
force_save_memory,
|
300
315
|
)
|
301
316
|
|
317
|
+
# 初始化事件总线需先于管理器,以便管理器在构造中安全订阅事件
|
318
|
+
self.event_bus = EventBus()
|
302
319
|
# 初始化管理器
|
303
320
|
self.memory_manager = MemoryManager(self)
|
304
321
|
self.task_analyzer = TaskAnalyzer(self)
|
305
322
|
self.file_methodology_manager = FileMethodologyManager(self)
|
323
|
+
self.prompt_manager = PromptManager(self)
|
306
324
|
|
307
325
|
# 设置系统提示词
|
308
326
|
self._setup_system_prompt()
|
@@ -340,16 +358,10 @@ class Agent:
|
|
340
358
|
use_tools: List[str],
|
341
359
|
):
|
342
360
|
"""初始化各种处理器"""
|
343
|
-
|
344
|
-
|
345
|
-
self.output_handler = output_handler if output_handler else [ToolRegistry()]
|
361
|
+
self.output_handler = output_handler or [ToolRegistry()]
|
346
362
|
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
|
-
)
|
363
|
+
self.input_handler = input_handler or []
|
364
|
+
self.multiline_inputer = multiline_inputer or get_multiline_input
|
353
365
|
|
354
366
|
def _init_config(
|
355
367
|
self,
|
@@ -361,47 +373,57 @@ class Agent:
|
|
361
373
|
force_save_memory: Optional[bool],
|
362
374
|
):
|
363
375
|
"""初始化配置选项"""
|
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
|
-
)
|
376
|
+
# 使用集中配置解析,保持与原逻辑一致
|
377
|
+
cfg = AgentConfig(
|
378
|
+
system_prompt=self.system_prompt,
|
379
|
+
name=self.name,
|
380
|
+
description=self.description,
|
381
|
+
model_group=model_group,
|
382
|
+
auto_complete=self.auto_complete,
|
383
|
+
need_summary=self.need_summary,
|
384
|
+
summary_prompt=summary_prompt,
|
385
|
+
execute_tool_confirm=execute_tool_confirm,
|
386
|
+
use_methodology=use_methodology,
|
387
|
+
use_analysis=use_analysis,
|
388
|
+
force_save_memory=force_save_memory,
|
389
|
+
files=self.files,
|
390
|
+
max_token_count=None,
|
391
|
+
).resolve_defaults()
|
392
|
+
|
393
|
+
# 将解析结果回填到 Agent 实例属性,保持向后兼容
|
394
|
+
self.use_methodology = bool(cfg.use_methodology)
|
395
|
+
self.use_analysis = bool(cfg.use_analysis)
|
396
|
+
self.execute_tool_confirm = bool(cfg.execute_tool_confirm)
|
397
|
+
self.summary_prompt = cfg.summary_prompt or DEFAULT_SUMMARY_PROMPT
|
398
|
+
self.max_token_count = int(cfg.max_token_count or get_max_token_count(model_group))
|
399
|
+
self.force_save_memory = bool(cfg.force_save_memory)
|
400
|
+
|
401
|
+
# 聚合配置到 AgentConfig,作为后续单一事实来源(保持兼容,不改变既有属性使用)
|
402
|
+
self.config = cfg
|
394
403
|
|
395
404
|
def _setup_system_prompt(self):
|
396
405
|
"""设置系统提示词"""
|
397
|
-
|
398
|
-
|
399
|
-
|
406
|
+
try:
|
407
|
+
if hasattr(self, "prompt_manager"):
|
408
|
+
prompt_text = self.prompt_manager.build_system_prompt()
|
409
|
+
else:
|
410
|
+
action_prompt = self.get_tool_usage_prompt()
|
411
|
+
prompt_text = f"""
|
400
412
|
{self.system_prompt}
|
401
413
|
|
402
414
|
{action_prompt}
|
403
415
|
"""
|
404
|
-
|
416
|
+
self.model.set_system_prompt(prompt_text) # type: ignore
|
417
|
+
except Exception:
|
418
|
+
# 回退到原始行为,确保兼容性
|
419
|
+
action_prompt = self.get_tool_usage_prompt()
|
420
|
+
self.model.set_system_prompt( # type: ignore
|
421
|
+
f"""
|
422
|
+
{self.system_prompt}
|
423
|
+
|
424
|
+
{action_prompt}
|
425
|
+
"""
|
426
|
+
)
|
405
427
|
|
406
428
|
def set_user_data(self, key: str, value: Any):
|
407
429
|
"""Sets user data in the session."""
|
@@ -413,8 +435,6 @@ class Agent:
|
|
413
435
|
|
414
436
|
def set_use_tools(self, use_tools):
|
415
437
|
"""设置要使用的工具列表"""
|
416
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
417
|
-
|
418
438
|
for handler in self.output_handler:
|
419
439
|
if isinstance(handler, ToolRegistry):
|
420
440
|
if use_tools:
|
@@ -435,6 +455,9 @@ class Agent:
|
|
435
455
|
If the configured multiline_inputer supports 'print_on_empty' keyword, pass it;
|
436
456
|
otherwise, fall back to calling with a single argument for compatibility.
|
437
457
|
"""
|
458
|
+
# 优先通过用户交互封装,便于未来替换 UI
|
459
|
+
if hasattr(self, "user_interaction"):
|
460
|
+
return self.user_interaction.multiline_input(tip, print_on_empty)
|
438
461
|
try:
|
439
462
|
# Try to pass the keyword for enhanced input handler
|
440
463
|
return self.multiline_inputer(tip, print_on_empty=print_on_empty) # type: ignore
|
@@ -463,13 +486,15 @@ class Agent:
|
|
463
486
|
|
464
487
|
def get_tool_registry(self) -> Optional[Any]:
|
465
488
|
"""获取工具注册表实例"""
|
466
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
467
|
-
|
468
489
|
for handler in self.output_handler:
|
469
490
|
if isinstance(handler, ToolRegistry):
|
470
491
|
return handler
|
471
492
|
return None
|
472
493
|
|
494
|
+
def get_event_bus(self) -> EventBus:
|
495
|
+
"""获取事件总线实例"""
|
496
|
+
return self.event_bus
|
497
|
+
|
473
498
|
def _call_model(
|
474
499
|
self, message: str, need_complete: bool = False, run_input_handlers: bool = True
|
475
500
|
) -> str:
|
@@ -518,11 +543,38 @@ class Agent:
|
|
518
543
|
|
519
544
|
def _add_addon_prompt(self, message: str, need_complete: bool) -> str:
|
520
545
|
"""添加附加提示到消息"""
|
546
|
+
# 广播添加附加提示前事件(不影响主流程)
|
547
|
+
try:
|
548
|
+
self.event_bus.emit(
|
549
|
+
"before_addon_prompt",
|
550
|
+
agent=self,
|
551
|
+
need_complete=need_complete,
|
552
|
+
current_message=message,
|
553
|
+
has_session_addon=bool(self.session.addon_prompt),
|
554
|
+
)
|
555
|
+
except Exception:
|
556
|
+
pass
|
557
|
+
|
558
|
+
addon_text = ""
|
521
559
|
if self.session.addon_prompt:
|
522
|
-
|
560
|
+
addon_text = self.session.addon_prompt
|
561
|
+
message += f"\n\n{addon_text}"
|
523
562
|
self.session.addon_prompt = ""
|
524
563
|
else:
|
525
|
-
|
564
|
+
addon_text = self.make_default_addon_prompt(need_complete)
|
565
|
+
message += f"\n\n{addon_text}"
|
566
|
+
|
567
|
+
# 广播添加附加提示后事件(不影响主流程)
|
568
|
+
try:
|
569
|
+
self.event_bus.emit(
|
570
|
+
"after_addon_prompt",
|
571
|
+
agent=self,
|
572
|
+
need_complete=need_complete,
|
573
|
+
addon_text=addon_text,
|
574
|
+
final_message=message,
|
575
|
+
)
|
576
|
+
except Exception:
|
577
|
+
pass
|
526
578
|
return message
|
527
579
|
|
528
580
|
def _manage_conversation_length(self, message: str) -> str:
|
@@ -542,7 +594,29 @@ class Agent:
|
|
542
594
|
if not self.model:
|
543
595
|
raise RuntimeError("Model not initialized")
|
544
596
|
|
597
|
+
# 事件:模型调用前
|
598
|
+
try:
|
599
|
+
self.event_bus.emit(
|
600
|
+
"before_model_call",
|
601
|
+
agent=self,
|
602
|
+
message=message,
|
603
|
+
)
|
604
|
+
except Exception:
|
605
|
+
pass
|
606
|
+
|
545
607
|
response = self.model.chat_until_success(message) # type: ignore
|
608
|
+
|
609
|
+
# 事件:模型调用后
|
610
|
+
try:
|
611
|
+
self.event_bus.emit(
|
612
|
+
"after_model_call",
|
613
|
+
agent=self,
|
614
|
+
message=message,
|
615
|
+
response=response,
|
616
|
+
)
|
617
|
+
except Exception:
|
618
|
+
pass
|
619
|
+
|
546
620
|
self.session.conversation_length += get_context_token_count(response)
|
547
621
|
|
548
622
|
return response
|
@@ -586,12 +660,11 @@ class Agent:
|
|
586
660
|
注意:
|
587
661
|
当上下文长度超过最大值时使用
|
588
662
|
"""
|
589
|
-
#
|
663
|
+
# 在清理历史之前,提示用户保存重要记忆(事件驱动触发实际保存)
|
590
664
|
if self.force_save_memory:
|
591
665
|
PrettyOutput.print(
|
592
666
|
"对话历史即将被总结和清理,请先保存重要信息...", OutputType.INFO
|
593
667
|
)
|
594
|
-
self.memory_manager.prompt_memory_save()
|
595
668
|
|
596
669
|
if self._should_use_file_upload():
|
597
670
|
return self._handle_history_with_file_upload()
|
@@ -613,17 +686,38 @@ class Agent:
|
|
613
686
|
|
614
687
|
# 清理历史(但不清理prompt,因为prompt会在builtin_input_handler中设置)
|
615
688
|
if self.model:
|
689
|
+
# 广播清理历史前事件
|
690
|
+
try:
|
691
|
+
self.event_bus.emit("before_history_clear", agent=self)
|
692
|
+
except Exception:
|
693
|
+
pass
|
616
694
|
self.model.reset()
|
617
695
|
# 重置后重新设置系统提示词,确保系统约束仍然生效
|
618
696
|
self._setup_system_prompt()
|
619
697
|
# 重置会话
|
620
698
|
self.session.clear_history()
|
699
|
+
# 广播清理历史后的事件
|
700
|
+
try:
|
701
|
+
self.event_bus.emit("after_history_clear", agent=self)
|
702
|
+
except Exception:
|
703
|
+
pass
|
621
704
|
|
622
705
|
return formatted_summary
|
623
706
|
|
624
707
|
def _handle_history_with_file_upload(self) -> str:
|
625
708
|
"""使用文件上传方式处理历史"""
|
626
|
-
|
709
|
+
# 广播清理历史前事件
|
710
|
+
try:
|
711
|
+
self.event_bus.emit("before_history_clear", agent=self)
|
712
|
+
except Exception:
|
713
|
+
pass
|
714
|
+
result = self.file_methodology_manager.handle_history_with_file_upload()
|
715
|
+
# 广播清理历史后的事件
|
716
|
+
try:
|
717
|
+
self.event_bus.emit("after_history_clear", agent=self)
|
718
|
+
except Exception:
|
719
|
+
pass
|
720
|
+
return result
|
627
721
|
|
628
722
|
def _format_summary_message(self, summary: str) -> str:
|
629
723
|
"""格式化摘要消息"""
|
@@ -654,30 +748,56 @@ class Agent:
|
|
654
748
|
2. 对于子Agent: 可能会生成总结(如果启用)
|
655
749
|
3. 使用spinner显示生成状态
|
656
750
|
"""
|
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
|
-
|
751
|
+
# 事件驱动方式:
|
752
|
+
# - TaskAnalyzer 通过订阅 before_summary/task_completed 事件执行分析与满意度收集
|
753
|
+
# - MemoryManager 通过订阅 before_history_clear/task_completed 事件执行记忆保存(受 force_save_memory 控制)
|
754
|
+
# 为减少耦合,这里不再直接调用上述组件,保持行为由事件触发
|
669
755
|
self._check_and_organize_memory()
|
670
756
|
|
757
|
+
result = "任务完成"
|
758
|
+
|
671
759
|
if self.need_summary:
|
672
760
|
|
673
761
|
self.session.prompt = self.summary_prompt
|
762
|
+
# 广播将要生成总结事件
|
763
|
+
try:
|
764
|
+
self.event_bus.emit(
|
765
|
+
"before_summary",
|
766
|
+
agent=self,
|
767
|
+
prompt=self.session.prompt,
|
768
|
+
auto_completed=auto_completed,
|
769
|
+
need_summary=self.need_summary,
|
770
|
+
)
|
771
|
+
except Exception:
|
772
|
+
pass
|
773
|
+
|
674
774
|
if not self.model:
|
675
775
|
raise RuntimeError("Model not initialized")
|
676
776
|
ret = self.model.chat_until_success(self.session.prompt) # type: ignore
|
777
|
+
result = ret
|
778
|
+
|
779
|
+
# 广播完成总结事件
|
780
|
+
try:
|
781
|
+
self.event_bus.emit(
|
782
|
+
"after_summary",
|
783
|
+
agent=self,
|
784
|
+
summary=result,
|
785
|
+
)
|
786
|
+
except Exception:
|
787
|
+
pass
|
677
788
|
|
678
|
-
|
789
|
+
# 广播任务完成事件(不影响主流程)
|
790
|
+
try:
|
791
|
+
self.event_bus.emit(
|
792
|
+
"task_completed",
|
793
|
+
agent=self,
|
794
|
+
auto_completed=auto_completed,
|
795
|
+
need_summary=self.need_summary,
|
796
|
+
)
|
797
|
+
except Exception:
|
798
|
+
pass
|
679
799
|
|
680
|
-
return
|
800
|
+
return result
|
681
801
|
|
682
802
|
def make_default_addon_prompt(self, need_complete: bool) -> str:
|
683
803
|
"""生成附加提示。
|
@@ -686,6 +806,10 @@ class Agent:
|
|
686
806
|
need_complete: 是否需要完成任务
|
687
807
|
|
688
808
|
"""
|
809
|
+
# 优先使用 PromptManager 以保持逻辑集中
|
810
|
+
if hasattr(self, "prompt_manager"):
|
811
|
+
return self.prompt_manager.build_default_addon_prompt(need_complete)
|
812
|
+
|
689
813
|
# 结构化系统指令
|
690
814
|
action_handlers = ", ".join([handler.name() for handler in self.output_handler])
|
691
815
|
|
@@ -737,6 +861,17 @@ class Agent:
|
|
737
861
|
self.session.prompt = f"{user_input}"
|
738
862
|
try:
|
739
863
|
set_agent(self.name, self)
|
864
|
+
# 广播任务开始事件(不影响主流程)
|
865
|
+
try:
|
866
|
+
self.event_bus.emit(
|
867
|
+
"task_started",
|
868
|
+
agent=self,
|
869
|
+
name=self.name,
|
870
|
+
description=self.description,
|
871
|
+
user_input=self.session.prompt,
|
872
|
+
)
|
873
|
+
except Exception:
|
874
|
+
pass
|
740
875
|
return self._main_loop()
|
741
876
|
except Exception as e:
|
742
877
|
PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
|
@@ -744,81 +879,17 @@ class Agent:
|
|
744
879
|
|
745
880
|
def _main_loop(self) -> Any:
|
746
881
|
"""主运行循环"""
|
747
|
-
|
748
|
-
|
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
|
-
)
|
882
|
+
# 委派至独立的运行循环类,保持行为一致
|
883
|
+
loop = AgentRunLoop(self)
|
884
|
+
return loop.run()
|
764
885
|
|
765
|
-
|
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]]]:
|
886
|
+
def _handle_run_interrupt(self, current_response: str) -> Optional[Union[Any, "LoopAction"]]:
|
816
887
|
"""处理运行中的中断
|
817
888
|
|
818
889
|
返回:
|
819
|
-
None:
|
820
|
-
Any:
|
821
|
-
|
890
|
+
None: 无中断,或中断后允许继续执行当前响应
|
891
|
+
Any: 需要返回的最终结果
|
892
|
+
LoopAction.SKIP_TURN: 中断后需要跳过当前响应,并立即开始下一次循环
|
822
893
|
"""
|
823
894
|
if not get_interrupt():
|
824
895
|
return None
|
@@ -827,6 +898,16 @@ class Agent:
|
|
827
898
|
user_input = self._multiline_input(
|
828
899
|
"模型交互期间被中断,请输入用户干预信息:", False
|
829
900
|
)
|
901
|
+
# 广播中断事件(包含用户输入,可能为空字符串)
|
902
|
+
try:
|
903
|
+
self.event_bus.emit(
|
904
|
+
"interrupt_triggered",
|
905
|
+
agent=self,
|
906
|
+
current_response=current_response,
|
907
|
+
user_input=user_input,
|
908
|
+
)
|
909
|
+
except Exception:
|
910
|
+
pass
|
830
911
|
|
831
912
|
self.run_input_handlers_next_turn = True
|
832
913
|
|
@@ -840,16 +921,16 @@ class Agent:
|
|
840
921
|
return None # 继续执行工具调用
|
841
922
|
else:
|
842
923
|
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
843
|
-
return
|
924
|
+
return LoopAction.SKIP_TURN # 请求主循环 continue
|
844
925
|
else:
|
845
926
|
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}"
|
846
|
-
return
|
927
|
+
return LoopAction.SKIP_TURN # 请求主循环 continue
|
847
928
|
|
848
|
-
def _get_next_user_action(self) -> str:
|
929
|
+
def _get_next_user_action(self) -> Union[str, "LoopAction"]:
|
849
930
|
"""获取用户下一步操作
|
850
931
|
|
851
932
|
返回:
|
852
|
-
|
933
|
+
LoopAction.CONTINUE 或 LoopAction.COMPLETE(兼容旧字符串值 "continue"/"complete")
|
853
934
|
"""
|
854
935
|
user_input = self._multiline_input(
|
855
936
|
f"{self.name}: 请输入,或输入空行来结束当前任务:", False
|
@@ -857,9 +938,10 @@ class Agent:
|
|
857
938
|
|
858
939
|
if user_input:
|
859
940
|
self.session.prompt = user_input
|
860
|
-
|
941
|
+
# 使用显式动作信号,保留返回类型注释以保持兼容
|
942
|
+
return LoopAction.CONTINUE # type: ignore[return-value]
|
861
943
|
else:
|
862
|
-
return
|
944
|
+
return LoopAction.COMPLETE # type: ignore[return-value]
|
863
945
|
|
864
946
|
def _first_run(self):
|
865
947
|
"""首次运行初始化"""
|
@@ -879,11 +961,20 @@ class Agent:
|
|
879
961
|
|
880
962
|
self.first = False
|
881
963
|
|
964
|
+
def _create_temp_model(self, system_prompt: str) -> BasePlatform:
|
965
|
+
"""创建一个用于执行一次性任务的临时模型实例,以避免污染主会话。"""
|
966
|
+
temp_model = PlatformRegistry().create_platform(
|
967
|
+
self.model.platform_name() # type: ignore
|
968
|
+
)
|
969
|
+
if not temp_model:
|
970
|
+
raise RuntimeError("创建临时模型失败。")
|
971
|
+
|
972
|
+
temp_model.set_model_name(self.model.name()) # type: ignore
|
973
|
+
temp_model.set_system_prompt(system_prompt)
|
974
|
+
return temp_model
|
975
|
+
|
882
976
|
def _filter_tools_if_needed(self, task: str):
|
883
977
|
"""如果工具数量超过阈值,使用大模型筛选相关工具"""
|
884
|
-
import re
|
885
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
886
|
-
|
887
978
|
tool_registry = self.get_tool_registry()
|
888
979
|
if not isinstance(tool_registry, ToolRegistry):
|
889
980
|
return
|
@@ -917,19 +1008,21 @@ class Agent:
|
|
917
1008
|
PrettyOutput.print(
|
918
1009
|
f"工具数量超过{threshold}个,正在使用AI筛选相关工具...", OutputType.INFO
|
919
1010
|
)
|
920
|
-
|
921
|
-
# 使用临时模型实例调用模型,以避免污染历史记录
|
1011
|
+
# 广播工具筛选开始事件
|
922
1012
|
try:
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
1013
|
+
self.event_bus.emit(
|
1014
|
+
"before_tool_filter",
|
1015
|
+
agent=self,
|
1016
|
+
task=task,
|
1017
|
+
total_tools=len(all_tools),
|
1018
|
+
threshold=threshold,
|
927
1019
|
)
|
928
|
-
|
929
|
-
|
1020
|
+
except Exception:
|
1021
|
+
pass
|
930
1022
|
|
931
|
-
|
932
|
-
|
1023
|
+
# 使用临时模型实例调用模型,以避免污染历史记录
|
1024
|
+
try:
|
1025
|
+
temp_model = self._create_temp_model("你是一个帮助筛选工具的助手。")
|
933
1026
|
selected_tools_str = temp_model.chat_until_success(
|
934
1027
|
selection_prompt
|
935
1028
|
) # type: ignore
|
@@ -954,10 +1047,34 @@ class Agent:
|
|
954
1047
|
f"已筛选出 {len(selected_tool_names)} 个相关工具: {', '.join(selected_tool_names)}",
|
955
1048
|
OutputType.SUCCESS,
|
956
1049
|
)
|
1050
|
+
# 广播工具筛选事件
|
1051
|
+
try:
|
1052
|
+
self.event_bus.emit(
|
1053
|
+
"tool_filtered",
|
1054
|
+
agent=self,
|
1055
|
+
task=task,
|
1056
|
+
selected_tools=selected_tool_names,
|
1057
|
+
total_tools=len(all_tools),
|
1058
|
+
threshold=threshold,
|
1059
|
+
)
|
1060
|
+
except Exception:
|
1061
|
+
pass
|
957
1062
|
else:
|
958
1063
|
PrettyOutput.print(
|
959
1064
|
"AI 未能筛选出任何相关工具,将使用所有工具。", OutputType.WARNING
|
960
1065
|
)
|
1066
|
+
# 广播工具筛选事件(无筛选结果)
|
1067
|
+
try:
|
1068
|
+
self.event_bus.emit(
|
1069
|
+
"tool_filtered",
|
1070
|
+
agent=self,
|
1071
|
+
task=task,
|
1072
|
+
selected_tools=[],
|
1073
|
+
total_tools=len(all_tools),
|
1074
|
+
threshold=threshold,
|
1075
|
+
)
|
1076
|
+
except Exception:
|
1077
|
+
pass
|
961
1078
|
|
962
1079
|
except Exception as e:
|
963
1080
|
PrettyOutput.print(
|