jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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 +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/__init__.py
CHANGED
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
import datetime
|
|
4
4
|
import os
|
|
5
5
|
import platform
|
|
6
|
-
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
# 第三方库导入
|
|
14
|
+
from rich.align import Align
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.text import Text
|
|
9
18
|
|
|
10
19
|
# 本地库导入
|
|
11
20
|
# jarvis_agent 相关
|
|
@@ -13,11 +22,44 @@ from jarvis.jarvis_agent.prompt_builder import build_action_prompt
|
|
|
13
22
|
from jarvis.jarvis_agent.protocols import OutputHandlerProtocol
|
|
14
23
|
from jarvis.jarvis_agent.session_manager import SessionManager
|
|
15
24
|
from jarvis.jarvis_agent.tool_executor import execute_tool_call
|
|
25
|
+
from jarvis.jarvis_agent.memory_manager import MemoryManager
|
|
26
|
+
from jarvis.jarvis_memory_organizer.memory_organizer import MemoryOrganizer
|
|
27
|
+
from jarvis.jarvis_agent.task_analyzer import TaskAnalyzer
|
|
28
|
+
from jarvis.jarvis_agent.task_planner import TaskPlanner
|
|
29
|
+
from jarvis.jarvis_agent.file_methodology_manager import FileMethodologyManager
|
|
16
30
|
from jarvis.jarvis_agent.prompts import (
|
|
17
31
|
DEFAULT_SUMMARY_PROMPT,
|
|
18
32
|
SUMMARY_REQUEST_PROMPT,
|
|
19
|
-
TASK_ANALYSIS_PROMPT,
|
|
33
|
+
TASK_ANALYSIS_PROMPT as TASK_ANALYSIS_PROMPT,
|
|
34
|
+
)
|
|
35
|
+
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
36
|
+
from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
|
|
37
|
+
from jarvis.jarvis_agent.rewrite_file_handler import RewriteFileHandler
|
|
38
|
+
from jarvis.jarvis_agent.prompt_manager import PromptManager
|
|
39
|
+
from jarvis.jarvis_agent.event_bus import EventBus
|
|
40
|
+
from jarvis.jarvis_agent.run_loop import AgentRunLoop
|
|
41
|
+
from jarvis.jarvis_agent.events import (
|
|
42
|
+
BEFORE_SUMMARY,
|
|
43
|
+
AFTER_SUMMARY,
|
|
44
|
+
TASK_COMPLETED,
|
|
45
|
+
TASK_STARTED,
|
|
46
|
+
BEFORE_ADDON_PROMPT,
|
|
47
|
+
AFTER_ADDON_PROMPT,
|
|
48
|
+
BEFORE_HISTORY_CLEAR,
|
|
49
|
+
AFTER_HISTORY_CLEAR,
|
|
50
|
+
BEFORE_MODEL_CALL,
|
|
51
|
+
AFTER_MODEL_CALL,
|
|
52
|
+
INTERRUPT_TRIGGERED,
|
|
53
|
+
BEFORE_TOOL_FILTER,
|
|
54
|
+
TOOL_FILTERED,
|
|
55
|
+
AFTER_TOOL_CALL,
|
|
20
56
|
)
|
|
57
|
+
from jarvis.jarvis_agent.user_interaction import UserInteractionHandler
|
|
58
|
+
from jarvis.jarvis_agent.utils import join_prompts
|
|
59
|
+
from jarvis.jarvis_utils.methodology import _load_all_methodologies
|
|
60
|
+
from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
|
|
61
|
+
from jarvis.jarvis_agent.file_context_handler import file_context_handler
|
|
62
|
+
from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
|
|
21
63
|
|
|
22
64
|
# jarvis_platform 相关
|
|
23
65
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
@@ -25,14 +67,18 @@ from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
|
25
67
|
|
|
26
68
|
# jarvis_utils 相关
|
|
27
69
|
from jarvis.jarvis_utils.config import (
|
|
28
|
-
|
|
70
|
+
get_data_dir,
|
|
29
71
|
get_normal_model_name,
|
|
30
72
|
get_normal_platform_name,
|
|
31
|
-
get_thinking_model_name,
|
|
32
|
-
get_thinking_platform_name,
|
|
33
73
|
is_execute_tool_confirm,
|
|
74
|
+
is_force_save_memory,
|
|
34
75
|
is_use_analysis,
|
|
35
76
|
is_use_methodology,
|
|
77
|
+
get_tool_filter_threshold,
|
|
78
|
+
get_after_tool_call_cb_dirs,
|
|
79
|
+
get_plan_max_depth,
|
|
80
|
+
is_plan_enabled,
|
|
81
|
+
get_addon_prompt_threshold,
|
|
36
82
|
)
|
|
37
83
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
|
38
84
|
from jarvis.jarvis_utils.globals import (
|
|
@@ -43,9 +89,99 @@ from jarvis.jarvis_utils.globals import (
|
|
|
43
89
|
set_interrupt,
|
|
44
90
|
)
|
|
45
91
|
from jarvis.jarvis_utils.input import get_multiline_input, user_confirm
|
|
46
|
-
from jarvis.jarvis_utils.methodology import load_methodology, upload_methodology
|
|
47
92
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
48
|
-
from jarvis.jarvis_utils.tag import
|
|
93
|
+
from jarvis.jarvis_utils.tag import ot
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def show_agent_startup_stats(
|
|
97
|
+
agent_name: str,
|
|
98
|
+
model_name: str,
|
|
99
|
+
tool_registry_instance: Optional[Any] = None,
|
|
100
|
+
platform_name: Optional[str] = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""输出启动时的统计信息
|
|
103
|
+
|
|
104
|
+
参数:
|
|
105
|
+
agent_name: Agent的名称
|
|
106
|
+
model_name: 使用的模型名称
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
methodologies = _load_all_methodologies()
|
|
110
|
+
methodology_count = len(methodologies)
|
|
111
|
+
|
|
112
|
+
# 获取工具数量
|
|
113
|
+
# 创建一个临时的工具注册表类来获取所有工具(不应用过滤)
|
|
114
|
+
class TempToolRegistry(ToolRegistry):
|
|
115
|
+
def _apply_tool_config_filter(self) -> None:
|
|
116
|
+
"""重写过滤方法,不执行任何过滤"""
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
# 获取所有工具的数量
|
|
120
|
+
tool_registry_all = TempToolRegistry()
|
|
121
|
+
total_tool_count = len(tool_registry_all.tools)
|
|
122
|
+
|
|
123
|
+
# 获取可用工具的数量(应用过滤)
|
|
124
|
+
if tool_registry_instance is not None:
|
|
125
|
+
available_tool_count = len(tool_registry_instance.get_all_tools())
|
|
126
|
+
else:
|
|
127
|
+
tool_registry = ToolRegistry()
|
|
128
|
+
available_tool_count = len(tool_registry.get_all_tools())
|
|
129
|
+
|
|
130
|
+
global_memory_dir = Path(get_data_dir()) / "memory" / "global_long_term"
|
|
131
|
+
global_memory_count = 0
|
|
132
|
+
if global_memory_dir.exists():
|
|
133
|
+
global_memory_count = len(list(global_memory_dir.glob("*.json")))
|
|
134
|
+
|
|
135
|
+
# 检查项目记忆
|
|
136
|
+
project_memory_dir = Path(".jarvis/memory")
|
|
137
|
+
project_memory_count = 0
|
|
138
|
+
if project_memory_dir.exists():
|
|
139
|
+
project_memory_count = len(list(project_memory_dir.glob("*.json")))
|
|
140
|
+
|
|
141
|
+
# 获取当前工作目录
|
|
142
|
+
current_dir = os.getcwd()
|
|
143
|
+
|
|
144
|
+
# 构建欢迎信息
|
|
145
|
+
platform = platform_name or get_normal_platform_name()
|
|
146
|
+
welcome_message = f"{agent_name} 初始化完成 - 使用 {platform} 平台 {model_name} 模型"
|
|
147
|
+
|
|
148
|
+
stats_parts = [
|
|
149
|
+
f"📚 本地方法论: [bold cyan]{methodology_count}[/bold cyan]",
|
|
150
|
+
f"🛠️ 工具: [bold green]{available_tool_count}/{total_tool_count}[/bold green] (可用/全部)",
|
|
151
|
+
f"🧠 全局记忆: [bold yellow]{global_memory_count}[/bold yellow]",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
# 如果有项目记忆,添加到统计信息中
|
|
155
|
+
if project_memory_count > 0:
|
|
156
|
+
stats_parts.append(
|
|
157
|
+
f"📝 项目记忆: [bold magenta]{project_memory_count}[/bold magenta]"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
stats_text = Text.from_markup(" | ".join(stats_parts), justify="center")
|
|
161
|
+
|
|
162
|
+
# 创建包含欢迎信息和统计信息的面板内容
|
|
163
|
+
panel_content = Text()
|
|
164
|
+
panel_content.append(welcome_message, style="bold white")
|
|
165
|
+
panel_content.append("\n")
|
|
166
|
+
panel_content.append(f"📁 工作目录: {current_dir}", style="dim white")
|
|
167
|
+
panel_content.append("\n\n")
|
|
168
|
+
panel_content.append(stats_text)
|
|
169
|
+
panel_content.justify = "center"
|
|
170
|
+
|
|
171
|
+
panel = Panel(
|
|
172
|
+
panel_content,
|
|
173
|
+
title="✨ Jarvis 资源概览 ✨",
|
|
174
|
+
title_align="center",
|
|
175
|
+
border_style="blue",
|
|
176
|
+
expand=False,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
console = Console()
|
|
180
|
+
console.print(Align.center(panel))
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
PrettyOutput.print(f"加载统计信息失败: {e}", OutputType.WARNING)
|
|
184
|
+
|
|
49
185
|
|
|
50
186
|
origin_agent_system_prompt = f"""
|
|
51
187
|
<role>
|
|
@@ -73,6 +209,21 @@ origin_agent_system_prompt = f"""
|
|
|
73
209
|
4. **完成**: 验证任务是否达成目标,并进行总结。
|
|
74
210
|
</workflow>
|
|
75
211
|
|
|
212
|
+
<sub_agents_guide>
|
|
213
|
+
# 子任务工具使用建议
|
|
214
|
+
- 使用 sub_code_agent(代码子Agent)当:
|
|
215
|
+
- 需要在当前任务下并行推进较大且相对独立的代码改造
|
|
216
|
+
- 涉及多文件/多模块的大范围变更,或需要较长的工具调用链
|
|
217
|
+
- 需要隔离上下文以避免污染当前对话(如探索性改动、PoC)
|
|
218
|
+
- 需要专注于单一代码子问题,阶段性产出可复用的结果
|
|
219
|
+
- 使用 sub_agent(通用子Agent)当:
|
|
220
|
+
- 子任务不是以代码改造为主(如调研、方案撰写、评审总结、用例设计、文档生成等)
|
|
221
|
+
- 只是需要短期分流一个轻量的辅助性子任务
|
|
222
|
+
说明:
|
|
223
|
+
- 两者仅需参数 task(可选 background 提供上下文),完成后返回结果给父Agent
|
|
224
|
+
- 子Agent将自动完成并生成总结,请在上层根据返回结果继续编排
|
|
225
|
+
</sub_agents_guide>
|
|
226
|
+
|
|
76
227
|
<system_info>
|
|
77
228
|
# 系统信息
|
|
78
229
|
- OS: {platform.platform()} {platform.version()}
|
|
@@ -81,134 +232,332 @@ origin_agent_system_prompt = f"""
|
|
|
81
232
|
"""
|
|
82
233
|
|
|
83
234
|
|
|
235
|
+
class LoopAction(Enum):
|
|
236
|
+
SKIP_TURN = "skip_turn"
|
|
237
|
+
CONTINUE = "continue"
|
|
238
|
+
COMPLETE = "complete"
|
|
239
|
+
|
|
240
|
+
|
|
84
241
|
class Agent:
|
|
85
|
-
|
|
242
|
+
# Attribute type annotations to satisfy static type checkers
|
|
243
|
+
event_bus: EventBus
|
|
244
|
+
memory_manager: MemoryManager
|
|
245
|
+
task_analyzer: TaskAnalyzer
|
|
246
|
+
file_methodology_manager: FileMethodologyManager
|
|
247
|
+
prompt_manager: PromptManager
|
|
248
|
+
model: BasePlatform
|
|
249
|
+
session: SessionManager
|
|
250
|
+
|
|
251
|
+
def clear_history(self):
|
|
86
252
|
"""
|
|
87
253
|
Clears the current conversation history by delegating to the session manager.
|
|
254
|
+
Emits BEFORE_HISTORY_CLEAR/AFTER_HISTORY_CLEAR and reapplies system prompt to preserve constraints.
|
|
88
255
|
"""
|
|
89
|
-
|
|
256
|
+
# 广播清理历史前事件(不影响主流程)
|
|
257
|
+
try:
|
|
258
|
+
self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
# 清理会话历史并重置模型状态
|
|
263
|
+
self.session.clear_history()
|
|
264
|
+
# 重置 addon_prompt 跳过轮数计数器
|
|
265
|
+
self._addon_prompt_skip_rounds = 0
|
|
266
|
+
|
|
267
|
+
# 重置后重新设置系统提示词,确保系统约束仍然生效
|
|
268
|
+
try:
|
|
269
|
+
self._setup_system_prompt()
|
|
270
|
+
except Exception:
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
# 广播清理历史后的事件
|
|
274
|
+
try:
|
|
275
|
+
self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
90
278
|
|
|
91
279
|
def __del__(self):
|
|
92
280
|
# 只有在记录启动时才停止记录
|
|
93
|
-
|
|
281
|
+
try:
|
|
282
|
+
name = getattr(self, "name", None)
|
|
283
|
+
if name:
|
|
284
|
+
delete_agent(name)
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
def get_tool_usage_prompt(self) -> str:
|
|
289
|
+
"""获取工具使用提示"""
|
|
290
|
+
return build_action_prompt(self.output_handler) # type: ignore
|
|
291
|
+
|
|
292
|
+
def __new__(cls, *args, **kwargs):
|
|
293
|
+
if kwargs.get("agent_type") == "code":
|
|
294
|
+
try:
|
|
295
|
+
from jarvis.jarvis_code_agent.code_agent import CodeAgent
|
|
296
|
+
except ImportError as e:
|
|
297
|
+
raise RuntimeError(
|
|
298
|
+
"CodeAgent could not be imported. Please ensure jarvis_code_agent is installed correctly."
|
|
299
|
+
) from e
|
|
300
|
+
|
|
301
|
+
# 移除 agent_type 避免无限循环,并传递所有其他参数
|
|
302
|
+
kwargs.pop("agent_type", None)
|
|
303
|
+
return CodeAgent(**kwargs)
|
|
304
|
+
else:
|
|
305
|
+
return super().__new__(cls)
|
|
94
306
|
|
|
95
307
|
def __init__(
|
|
96
308
|
self,
|
|
97
309
|
system_prompt: str,
|
|
98
310
|
name: str = "Jarvis",
|
|
99
311
|
description: str = "",
|
|
100
|
-
|
|
312
|
+
model_group: Optional[str] = None,
|
|
101
313
|
summary_prompt: Optional[str] = None,
|
|
102
314
|
auto_complete: bool = False,
|
|
103
|
-
output_handler: List[OutputHandlerProtocol] =
|
|
104
|
-
use_tools: List[str] =
|
|
105
|
-
input_handler: Optional[List[Callable[[str, Any], Tuple[str, bool]]]] = None,
|
|
315
|
+
output_handler: Optional[List[OutputHandlerProtocol]] = None,
|
|
316
|
+
use_tools: Optional[List[str]] = None,
|
|
106
317
|
execute_tool_confirm: Optional[bool] = None,
|
|
107
318
|
need_summary: bool = True,
|
|
319
|
+
auto_summary_rounds: Optional[int] = None,
|
|
108
320
|
multiline_inputer: Optional[Callable[[str], str]] = None,
|
|
109
321
|
use_methodology: Optional[bool] = None,
|
|
110
322
|
use_analysis: Optional[bool] = None,
|
|
111
|
-
|
|
323
|
+
force_save_memory: Optional[bool] = None,
|
|
324
|
+
disable_file_edit: bool = False,
|
|
325
|
+
files: Optional[List[str]] = None,
|
|
326
|
+
confirm_callback: Optional[Callable[[str, bool], bool]] = None,
|
|
327
|
+
non_interactive: Optional[bool] = None,
|
|
328
|
+
in_multi_agent: Optional[bool] = None,
|
|
329
|
+
plan: Optional[bool] = None,
|
|
330
|
+
plan_max_depth: Optional[int] = None,
|
|
331
|
+
plan_depth: int = 0,
|
|
332
|
+
agent_type: str = "normal",
|
|
333
|
+
**kwargs,
|
|
112
334
|
):
|
|
113
|
-
self.files = files
|
|
114
335
|
"""初始化Jarvis Agent实例
|
|
115
336
|
|
|
116
337
|
参数:
|
|
117
338
|
system_prompt: 系统提示词,定义Agent的行为准则
|
|
118
339
|
name: Agent名称,默认为"Jarvis"
|
|
119
340
|
description: Agent描述信息
|
|
120
|
-
|
|
341
|
+
|
|
121
342
|
summary_prompt: 任务总结提示模板
|
|
122
343
|
auto_complete: 是否自动完成任务
|
|
123
|
-
output_handler: 输出处理器列表
|
|
124
|
-
input_handler: 输入处理器列表
|
|
125
|
-
max_context_length: 最大上下文长度
|
|
126
344
|
execute_tool_confirm: 执行工具前是否需要确认
|
|
127
345
|
need_summary: 是否需要生成总结
|
|
128
346
|
multiline_inputer: 多行输入处理器
|
|
129
347
|
use_methodology: 是否使用方法论
|
|
130
348
|
use_analysis: 是否使用任务分析
|
|
349
|
+
force_save_memory: 是否强制保存记忆
|
|
350
|
+
confirm_callback: 用户确认回调函数,签名为 (tip: str, default: bool) -> bool;默认使用CLI的user_confirm
|
|
351
|
+
non_interactive: 是否以非交互模式运行(优先级最高,覆盖环境变量与配置)
|
|
352
|
+
plan: 是否启用任务规划与子任务拆分(默认从配置加载;启用后在进入主循环前评估是否需要将任务拆分为 <SUB_TASK> 列表,逐一由子Agent执行并汇总结果)
|
|
353
|
+
plan_max_depth: 任务规划的最大层数(默认3,可通过配置 JARVIS_PLAN_MAX_DEPTH 或入参覆盖)
|
|
354
|
+
plan_depth: 当前规划层数(内部用于递归控制,子Agent会在父基础上+1)
|
|
131
355
|
"""
|
|
356
|
+
# 基础属性初始化(仅根据入参设置原始值;实际生效的默认回退在 _init_config 中统一解析)
|
|
357
|
+
# 标识与描述
|
|
132
358
|
self.name = make_agent_name(name)
|
|
133
359
|
self.description = description
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
self.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
360
|
+
self.system_prompt = system_prompt
|
|
361
|
+
# 行为控制开关(原始入参值)
|
|
362
|
+
self.auto_complete = bool(auto_complete)
|
|
363
|
+
self.need_summary = bool(need_summary)
|
|
364
|
+
# 自动摘要轮次:None 表示使用配置文件中的默认值,由 AgentRunLoop 决定最终取值
|
|
365
|
+
self.auto_summary_rounds = auto_summary_rounds
|
|
366
|
+
self.use_methodology = use_methodology
|
|
367
|
+
self.use_analysis = use_analysis
|
|
368
|
+
self.execute_tool_confirm = execute_tool_confirm
|
|
369
|
+
self.summary_prompt = summary_prompt
|
|
370
|
+
self.force_save_memory = force_save_memory
|
|
371
|
+
self.disable_file_edit = bool(disable_file_edit)
|
|
372
|
+
# 资源与环境
|
|
373
|
+
self.model_group = model_group
|
|
374
|
+
self.files = files or []
|
|
375
|
+
self.use_tools = use_tools
|
|
376
|
+
self.non_interactive = non_interactive
|
|
377
|
+
# 多智能体运行标志:用于控制非交互模式下的自动完成行为
|
|
378
|
+
self.in_multi_agent = bool(in_multi_agent)
|
|
379
|
+
# 任务规划:优先使用入参,否则回退到配置
|
|
380
|
+
self.plan = bool(plan) if plan is not None else is_plan_enabled()
|
|
381
|
+
# 规划深度与上限
|
|
382
|
+
try:
|
|
383
|
+
self.plan_max_depth = (
|
|
384
|
+
int(plan_max_depth) if plan_max_depth is not None else int(get_plan_max_depth())
|
|
146
385
|
)
|
|
147
|
-
|
|
386
|
+
except Exception:
|
|
387
|
+
self.plan_max_depth = 2
|
|
388
|
+
try:
|
|
389
|
+
self.plan_depth = int(plan_depth)
|
|
390
|
+
except Exception:
|
|
391
|
+
self.plan_depth = 0
|
|
392
|
+
# 运行时状态
|
|
393
|
+
self.first = True
|
|
394
|
+
self.run_input_handlers_next_turn = False
|
|
395
|
+
self.user_data: Dict[str, Any] = {}
|
|
396
|
+
# 记录连续未添加 addon_prompt 的轮数
|
|
397
|
+
self._addon_prompt_skip_rounds: int = 0
|
|
148
398
|
|
|
149
|
-
if model_name:
|
|
150
|
-
self.model.set_model_name(model_name)
|
|
151
399
|
|
|
152
|
-
|
|
400
|
+
# 用户确认回调:默认使用 CLI 的 user_confirm,可由外部注入以支持 TUI/GUI
|
|
401
|
+
self.confirm_callback: Callable[[str, bool], bool] = (
|
|
402
|
+
confirm_callback or user_confirm # type: ignore[assignment]
|
|
403
|
+
)
|
|
153
404
|
|
|
154
|
-
|
|
405
|
+
# 初始化模型和会话
|
|
406
|
+
self._init_model(model_group)
|
|
407
|
+
self._init_session()
|
|
408
|
+
|
|
409
|
+
# 初始化处理器
|
|
410
|
+
self._init_handlers(
|
|
411
|
+
multiline_inputer,
|
|
412
|
+
output_handler,
|
|
413
|
+
use_tools or [],
|
|
414
|
+
)
|
|
415
|
+
# 初始化用户交互封装,保持向后兼容
|
|
416
|
+
self.user_interaction = UserInteractionHandler(self.multiline_inputer, self.confirm_callback)
|
|
417
|
+
# 将确认函数指向封装后的 confirm,保持既有调用不变
|
|
418
|
+
self.confirm_callback = self.user_interaction.confirm # type: ignore[assignment]
|
|
419
|
+
# 非交互模式参数支持:允许通过构造参数显式控制,便于其他Agent调用时设置
|
|
420
|
+
try:
|
|
421
|
+
# 优先使用构造参数,其次回退到环境变量
|
|
422
|
+
self.non_interactive = (
|
|
423
|
+
bool(non_interactive)
|
|
424
|
+
if non_interactive is not None
|
|
425
|
+
else str(os.environ.get("JARVIS_NON_INTERACTIVE", "")).lower() in ("1", "true", "yes")
|
|
426
|
+
)
|
|
427
|
+
# 如果构造参数显式提供,则同步到环境变量与全局配置,供下游组件读取
|
|
428
|
+
if non_interactive is not None:
|
|
429
|
+
os.environ["JARVIS_NON_INTERACTIVE"] = "true" if self.non_interactive else "false"
|
|
155
430
|
|
|
156
|
-
|
|
157
|
-
|
|
431
|
+
except Exception:
|
|
432
|
+
# 防御式回退
|
|
433
|
+
self.non_interactive = False
|
|
158
434
|
|
|
159
|
-
|
|
435
|
+
# 初始化配置(直接解析,不再依赖 _init_config)
|
|
436
|
+
try:
|
|
437
|
+
resolved_use_methodology = bool(use_methodology if use_methodology is not None else is_use_methodology())
|
|
438
|
+
except Exception:
|
|
439
|
+
resolved_use_methodology = bool(use_methodology) if use_methodology is not None else True
|
|
160
440
|
|
|
161
|
-
|
|
162
|
-
|
|
441
|
+
try:
|
|
442
|
+
resolved_use_analysis = bool(use_analysis if use_analysis is not None else is_use_analysis())
|
|
443
|
+
except Exception:
|
|
444
|
+
resolved_use_analysis = bool(use_analysis) if use_analysis is not None else True
|
|
163
445
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
446
|
+
try:
|
|
447
|
+
resolved_execute_tool_confirm = bool(execute_tool_confirm if execute_tool_confirm is not None else is_execute_tool_confirm())
|
|
448
|
+
except Exception:
|
|
449
|
+
resolved_execute_tool_confirm = bool(execute_tool_confirm) if execute_tool_confirm is not None else False
|
|
167
450
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
self.
|
|
177
|
-
|
|
451
|
+
try:
|
|
452
|
+
resolved_force_save_memory = bool(force_save_memory if force_save_memory is not None else is_force_save_memory())
|
|
453
|
+
except Exception:
|
|
454
|
+
resolved_force_save_memory = bool(force_save_memory) if force_save_memory is not None else False
|
|
455
|
+
|
|
456
|
+
self.use_methodology = resolved_use_methodology
|
|
457
|
+
self.use_analysis = resolved_use_analysis
|
|
458
|
+
self.execute_tool_confirm = resolved_execute_tool_confirm
|
|
459
|
+
self.summary_prompt = (summary_prompt or DEFAULT_SUMMARY_PROMPT)
|
|
460
|
+
self.force_save_memory = resolved_force_save_memory
|
|
461
|
+
# 多智能体模式下,默认不自动完成(即使是非交互),仅在明确传入 auto_complete=True 时开启
|
|
462
|
+
if self.in_multi_agent:
|
|
463
|
+
self.auto_complete = bool(self.auto_complete)
|
|
464
|
+
else:
|
|
465
|
+
# 非交互模式下默认自动完成;否则保持传入的 auto_complete 值
|
|
466
|
+
self.auto_complete = bool(self.auto_complete or (self.non_interactive or False))
|
|
467
|
+
|
|
468
|
+
# 初始化事件总线需先于管理器,以便管理器在构造中安全订阅事件
|
|
469
|
+
self.event_bus = EventBus()
|
|
470
|
+
# 初始化管理器
|
|
471
|
+
self.memory_manager = MemoryManager(self)
|
|
472
|
+
self.task_analyzer = TaskAnalyzer(self)
|
|
473
|
+
self.file_methodology_manager = FileMethodologyManager(self)
|
|
474
|
+
self.prompt_manager = PromptManager(self)
|
|
475
|
+
# 任务规划器:封装规划与子任务调度逻辑
|
|
476
|
+
self.task_planner = TaskPlanner(self, plan_depth=self.plan_depth, plan_max_depth=self.plan_max_depth)
|
|
477
|
+
|
|
478
|
+
# 设置系统提示词
|
|
479
|
+
self._setup_system_prompt()
|
|
480
|
+
|
|
481
|
+
# 输出统计信息(包含欢迎信息)
|
|
482
|
+
show_agent_startup_stats(
|
|
483
|
+
name,
|
|
484
|
+
self.model.name(),
|
|
485
|
+
self.get_tool_registry(), # type: ignore
|
|
486
|
+
platform_name=self.model.platform_name(), # type: ignore
|
|
178
487
|
)
|
|
179
|
-
|
|
180
|
-
self.
|
|
181
|
-
self.need_summary = need_summary
|
|
182
|
-
# Load configuration from environment variables
|
|
488
|
+
# 动态加载工具调用后回调
|
|
489
|
+
self._load_after_tool_callbacks()
|
|
183
490
|
|
|
184
|
-
|
|
491
|
+
def _init_model(self, model_group: Optional[str]):
|
|
492
|
+
"""初始化模型平台(统一使用 normal 平台/模型)"""
|
|
493
|
+
platform_name = get_normal_platform_name(model_group)
|
|
494
|
+
model_name = get_normal_model_name(model_group)
|
|
185
495
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
496
|
+
maybe_model = PlatformRegistry().create_platform(platform_name)
|
|
497
|
+
if maybe_model is None:
|
|
498
|
+
PrettyOutput.print(
|
|
499
|
+
f"平台 {platform_name} 不存在,将使用普通模型", OutputType.WARNING
|
|
500
|
+
)
|
|
501
|
+
maybe_model = PlatformRegistry().get_normal_platform()
|
|
191
502
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
503
|
+
# 在此处收敛为非可选类型,确保后续赋值满足类型检查
|
|
504
|
+
self.model = maybe_model
|
|
195
505
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
506
|
+
if model_name:
|
|
507
|
+
self.model.set_model_name(model_name)
|
|
508
|
+
|
|
509
|
+
self.model.set_model_group(model_group)
|
|
510
|
+
self.model.set_suppress_output(False)
|
|
199
511
|
|
|
200
|
-
|
|
512
|
+
def _init_session(self):
|
|
513
|
+
"""初始化会话管理器"""
|
|
514
|
+
self.session = SessionManager(model=self.model, agent_name=self.name) # type: ignore
|
|
201
515
|
|
|
202
|
-
|
|
516
|
+
def _init_handlers(
|
|
517
|
+
self,
|
|
518
|
+
multiline_inputer: Optional[Callable[[str], str]],
|
|
519
|
+
output_handler: Optional[List[OutputHandlerProtocol]],
|
|
520
|
+
use_tools: List[str],
|
|
521
|
+
):
|
|
522
|
+
"""初始化各种处理器"""
|
|
523
|
+
default_handlers: List[Any] = [ToolRegistry()]
|
|
524
|
+
if not getattr(self, "disable_file_edit", False):
|
|
525
|
+
default_handlers.extend([EditFileHandler(), RewriteFileHandler()])
|
|
526
|
+
handlers = output_handler or default_handlers
|
|
527
|
+
if getattr(self, "disable_file_edit", False):
|
|
528
|
+
handlers = [h for h in handlers if not isinstance(h, (EditFileHandler, RewriteFileHandler))]
|
|
529
|
+
self.output_handler = handlers
|
|
530
|
+
self.set_use_tools(use_tools)
|
|
531
|
+
self.input_handler = [
|
|
532
|
+
builtin_input_handler,
|
|
533
|
+
shell_input_handler,
|
|
534
|
+
file_context_handler,
|
|
535
|
+
]
|
|
536
|
+
self.multiline_inputer = multiline_inputer or get_multiline_input
|
|
537
|
+
|
|
538
|
+
def _setup_system_prompt(self):
|
|
539
|
+
"""设置系统提示词"""
|
|
540
|
+
try:
|
|
541
|
+
if hasattr(self, "prompt_manager"):
|
|
542
|
+
prompt_text = self.prompt_manager.build_system_prompt()
|
|
543
|
+
else:
|
|
544
|
+
action_prompt = self.get_tool_usage_prompt()
|
|
545
|
+
prompt_text = f"""
|
|
546
|
+
{self.system_prompt}
|
|
203
547
|
|
|
204
|
-
|
|
205
|
-
|
|
548
|
+
{action_prompt}
|
|
549
|
+
"""
|
|
550
|
+
self.model.set_system_prompt(prompt_text) # type: ignore
|
|
551
|
+
except Exception:
|
|
552
|
+
# 回退到原始行为,确保兼容性
|
|
553
|
+
action_prompt = self.get_tool_usage_prompt()
|
|
554
|
+
self.model.set_system_prompt( # type: ignore
|
|
555
|
+
f"""
|
|
206
556
|
{self.system_prompt}
|
|
207
557
|
|
|
208
558
|
{action_prompt}
|
|
209
559
|
"""
|
|
210
|
-
|
|
211
|
-
self.first = True
|
|
560
|
+
)
|
|
212
561
|
|
|
213
562
|
def set_user_data(self, key: str, value: Any):
|
|
214
563
|
"""Sets user data in the session."""
|
|
@@ -220,8 +569,6 @@ class Agent:
|
|
|
220
569
|
|
|
221
570
|
def set_use_tools(self, use_tools):
|
|
222
571
|
"""设置要使用的工具列表"""
|
|
223
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
224
|
-
|
|
225
572
|
for handler in self.output_handler:
|
|
226
573
|
if isinstance(handler, ToolRegistry):
|
|
227
574
|
if use_tools:
|
|
@@ -232,13 +579,115 @@ class Agent:
|
|
|
232
579
|
"""Sets the addon prompt in the session."""
|
|
233
580
|
self.session.set_addon_prompt(addon_prompt)
|
|
234
581
|
|
|
235
|
-
def
|
|
236
|
-
"""
|
|
582
|
+
def set_run_input_handlers_next_turn(self, value: bool):
|
|
583
|
+
"""Sets the flag to run input handlers on the next turn."""
|
|
584
|
+
self.run_input_handlers_next_turn = value
|
|
237
585
|
|
|
238
|
-
|
|
239
|
-
|
|
586
|
+
def _multiline_input(self, tip: str, print_on_empty: bool) -> str:
|
|
587
|
+
"""
|
|
588
|
+
Safe wrapper for multiline input to optionally suppress empty-input notice.
|
|
589
|
+
If the configured multiline_inputer supports 'print_on_empty' keyword, pass it;
|
|
590
|
+
otherwise, fall back to calling with a single argument for compatibility.
|
|
240
591
|
"""
|
|
241
|
-
|
|
592
|
+
# 优先通过用户交互封装,便于未来替换 UI
|
|
593
|
+
if hasattr(self, "user_interaction"):
|
|
594
|
+
return self.user_interaction.multiline_input(tip, print_on_empty)
|
|
595
|
+
try:
|
|
596
|
+
# Try to pass the keyword for enhanced input handler
|
|
597
|
+
return self.multiline_inputer(tip, print_on_empty=print_on_empty) # type: ignore
|
|
598
|
+
except TypeError:
|
|
599
|
+
# Fallback for custom handlers that only accept one argument
|
|
600
|
+
return self.multiline_inputer(tip) # type: ignore
|
|
601
|
+
|
|
602
|
+
def _load_after_tool_callbacks(self) -> None:
|
|
603
|
+
"""
|
|
604
|
+
扫描 JARVIS_AFTER_TOOL_CALL_CB_DIRS 中的 Python 文件并动态注册回调。
|
|
605
|
+
约定优先级(任一命中即注册):
|
|
606
|
+
- 模块级可调用对象: after_tool_call_cb
|
|
607
|
+
- 工厂方法返回单个或多个可调用对象: get_after_tool_call_cb(), register_after_tool_call_cb()
|
|
608
|
+
"""
|
|
609
|
+
try:
|
|
610
|
+
dirs = get_after_tool_call_cb_dirs()
|
|
611
|
+
if not dirs:
|
|
612
|
+
return
|
|
613
|
+
for d in dirs:
|
|
614
|
+
p_dir = Path(d)
|
|
615
|
+
if not p_dir.exists() or not p_dir.is_dir():
|
|
616
|
+
continue
|
|
617
|
+
for file_path in p_dir.glob("*.py"):
|
|
618
|
+
if file_path.name == "__init__.py":
|
|
619
|
+
continue
|
|
620
|
+
parent_dir = str(file_path.parent)
|
|
621
|
+
added_path = False
|
|
622
|
+
try:
|
|
623
|
+
if parent_dir not in sys.path:
|
|
624
|
+
sys.path.insert(0, parent_dir)
|
|
625
|
+
added_path = True
|
|
626
|
+
module_name = file_path.stem
|
|
627
|
+
module = __import__(module_name)
|
|
628
|
+
|
|
629
|
+
candidates: List[Callable[[Any], None]] = []
|
|
630
|
+
|
|
631
|
+
# 1) 直接导出的回调
|
|
632
|
+
if hasattr(module, "after_tool_call_cb"):
|
|
633
|
+
obj = getattr(module, "after_tool_call_cb")
|
|
634
|
+
if callable(obj):
|
|
635
|
+
candidates.append(obj) # type: ignore[arg-type]
|
|
636
|
+
|
|
637
|
+
# 2) 工厂方法:get_after_tool_call_cb()
|
|
638
|
+
if hasattr(module, "get_after_tool_call_cb"):
|
|
639
|
+
factory = getattr(module, "get_after_tool_call_cb")
|
|
640
|
+
if callable(factory):
|
|
641
|
+
try:
|
|
642
|
+
ret = factory()
|
|
643
|
+
if callable(ret):
|
|
644
|
+
candidates.append(ret)
|
|
645
|
+
elif isinstance(ret, (list, tuple)):
|
|
646
|
+
for c in ret:
|
|
647
|
+
if callable(c):
|
|
648
|
+
candidates.append(c)
|
|
649
|
+
except Exception:
|
|
650
|
+
pass
|
|
651
|
+
|
|
652
|
+
# 3) 工厂方法:register_after_tool_call_cb()
|
|
653
|
+
if hasattr(module, "register_after_tool_call_cb"):
|
|
654
|
+
factory2 = getattr(module, "register_after_tool_call_cb")
|
|
655
|
+
if callable(factory2):
|
|
656
|
+
try:
|
|
657
|
+
ret2 = factory2()
|
|
658
|
+
if callable(ret2):
|
|
659
|
+
candidates.append(ret2)
|
|
660
|
+
elif isinstance(ret2, (list, tuple)):
|
|
661
|
+
for c in ret2:
|
|
662
|
+
if callable(c):
|
|
663
|
+
candidates.append(c)
|
|
664
|
+
except Exception:
|
|
665
|
+
pass
|
|
666
|
+
|
|
667
|
+
for cb in candidates:
|
|
668
|
+
try:
|
|
669
|
+
def _make_wrapper(callback):
|
|
670
|
+
def _wrapper(**kwargs: Any) -> None:
|
|
671
|
+
try:
|
|
672
|
+
agent = kwargs.get("agent")
|
|
673
|
+
callback(agent)
|
|
674
|
+
except Exception:
|
|
675
|
+
pass
|
|
676
|
+
return _wrapper
|
|
677
|
+
self.event_bus.subscribe(AFTER_TOOL_CALL, _make_wrapper(cb))
|
|
678
|
+
except Exception:
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
except Exception as e:
|
|
682
|
+
PrettyOutput.print(f"从 {file_path} 加载回调失败: {e}", OutputType.WARNING)
|
|
683
|
+
finally:
|
|
684
|
+
if added_path:
|
|
685
|
+
try:
|
|
686
|
+
sys.path.remove(parent_dir)
|
|
687
|
+
except ValueError:
|
|
688
|
+
pass
|
|
689
|
+
except Exception as e:
|
|
690
|
+
PrettyOutput.print(f"加载回调目录时发生错误: {e}", OutputType.WARNING)
|
|
242
691
|
|
|
243
692
|
def save_session(self) -> bool:
|
|
244
693
|
"""Saves the current session state by delegating to the session manager."""
|
|
@@ -253,19 +702,24 @@ class Agent:
|
|
|
253
702
|
|
|
254
703
|
def get_tool_registry(self) -> Optional[Any]:
|
|
255
704
|
"""获取工具注册表实例"""
|
|
256
|
-
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
257
|
-
|
|
258
705
|
for handler in self.output_handler:
|
|
259
706
|
if isinstance(handler, ToolRegistry):
|
|
260
707
|
return handler
|
|
261
708
|
return None
|
|
262
709
|
|
|
263
|
-
def
|
|
710
|
+
def get_event_bus(self) -> EventBus:
|
|
711
|
+
"""获取事件总线实例"""
|
|
712
|
+
return self.event_bus
|
|
713
|
+
|
|
714
|
+
def _call_model(
|
|
715
|
+
self, message: str, need_complete: bool = False, run_input_handlers: bool = True
|
|
716
|
+
) -> str:
|
|
264
717
|
"""调用AI模型并实现重试逻辑
|
|
265
718
|
|
|
266
719
|
参数:
|
|
267
720
|
message: 输入给模型的消息
|
|
268
721
|
need_complete: 是否需要完成任务标记
|
|
722
|
+
run_input_handlers: 是否运行输入处理器
|
|
269
723
|
|
|
270
724
|
返回:
|
|
271
725
|
str: 模型的响应
|
|
@@ -276,27 +730,136 @@ class Agent:
|
|
|
276
730
|
3. 会自动添加附加提示
|
|
277
731
|
4. 会检查并处理上下文长度限制
|
|
278
732
|
"""
|
|
733
|
+
# 处理输入
|
|
734
|
+
if run_input_handlers:
|
|
735
|
+
message = self._process_input(message)
|
|
736
|
+
if not message:
|
|
737
|
+
return ""
|
|
738
|
+
|
|
739
|
+
# 添加附加提示
|
|
740
|
+
message = self._add_addon_prompt(message, need_complete)
|
|
741
|
+
|
|
742
|
+
# 管理对话长度
|
|
743
|
+
message = self._manage_conversation_length(message)
|
|
744
|
+
|
|
745
|
+
# 调用模型
|
|
746
|
+
response = self._invoke_model(message)
|
|
747
|
+
|
|
748
|
+
return response
|
|
749
|
+
|
|
750
|
+
def _process_input(self, message: str) -> str:
|
|
751
|
+
"""处理输入消息"""
|
|
279
752
|
for handler in self.input_handler:
|
|
280
753
|
message, need_return = handler(message, self)
|
|
281
754
|
if need_return:
|
|
755
|
+
self._last_handler_returned = True
|
|
282
756
|
return message
|
|
757
|
+
self._last_handler_returned = False
|
|
758
|
+
return message
|
|
759
|
+
|
|
760
|
+
def _add_addon_prompt(self, message: str, need_complete: bool) -> str:
|
|
761
|
+
"""添加附加提示到消息
|
|
762
|
+
|
|
763
|
+
规则:
|
|
764
|
+
1. 如果 session.addon_prompt 存在,优先使用它
|
|
765
|
+
2. 如果消息长度超过阈值,添加默认 addon_prompt
|
|
766
|
+
3. 如果连续10轮都没有添加过 addon_prompt,强制添加一次
|
|
767
|
+
"""
|
|
768
|
+
# 广播添加附加提示前事件(不影响主流程)
|
|
769
|
+
try:
|
|
770
|
+
self.event_bus.emit(
|
|
771
|
+
BEFORE_ADDON_PROMPT,
|
|
772
|
+
agent=self,
|
|
773
|
+
need_complete=need_complete,
|
|
774
|
+
current_message=message,
|
|
775
|
+
has_session_addon=bool(self.session.addon_prompt),
|
|
776
|
+
)
|
|
777
|
+
except Exception:
|
|
778
|
+
pass
|
|
283
779
|
|
|
780
|
+
addon_text = ""
|
|
781
|
+
should_add = False
|
|
782
|
+
|
|
284
783
|
if self.session.addon_prompt:
|
|
285
|
-
|
|
784
|
+
# 优先使用 session 中设置的 addon_prompt
|
|
785
|
+
addon_text = self.session.addon_prompt
|
|
786
|
+
message = join_prompts([message, addon_text])
|
|
286
787
|
self.session.addon_prompt = ""
|
|
788
|
+
should_add = True
|
|
287
789
|
else:
|
|
288
|
-
|
|
790
|
+
threshold = get_addon_prompt_threshold()
|
|
791
|
+
# 条件1:消息长度超过阈值
|
|
792
|
+
if len(message) > threshold:
|
|
793
|
+
addon_text = self.make_default_addon_prompt(need_complete)
|
|
794
|
+
message = join_prompts([message, addon_text])
|
|
795
|
+
should_add = True
|
|
796
|
+
# 条件2:连续10轮都没有添加过 addon_prompt,强制添加一次
|
|
797
|
+
elif self._addon_prompt_skip_rounds >= 10:
|
|
798
|
+
addon_text = self.make_default_addon_prompt(need_complete)
|
|
799
|
+
message = join_prompts([message, addon_text])
|
|
800
|
+
should_add = True
|
|
801
|
+
|
|
802
|
+
# 更新计数器:如果添加了 addon_prompt,重置计数器;否则递增
|
|
803
|
+
if should_add:
|
|
804
|
+
self._addon_prompt_skip_rounds = 0
|
|
805
|
+
else:
|
|
806
|
+
self._addon_prompt_skip_rounds += 1
|
|
807
|
+
|
|
808
|
+
# 广播添加附加提示后事件(不影响主流程)
|
|
809
|
+
try:
|
|
810
|
+
self.event_bus.emit(
|
|
811
|
+
AFTER_ADDON_PROMPT,
|
|
812
|
+
agent=self,
|
|
813
|
+
need_complete=need_complete,
|
|
814
|
+
addon_text=addon_text,
|
|
815
|
+
final_message=message,
|
|
816
|
+
)
|
|
817
|
+
except Exception:
|
|
818
|
+
pass
|
|
819
|
+
return message
|
|
289
820
|
|
|
290
|
-
|
|
821
|
+
def _manage_conversation_length(self, message: str) -> str:
|
|
822
|
+
"""管理对话长度计数;摘要触发由轮次在 AgentRunLoop 中统一处理。"""
|
|
291
823
|
self.session.conversation_length += get_context_token_count(message)
|
|
292
824
|
|
|
293
|
-
if self.session.conversation_length > self.max_token_count:
|
|
294
|
-
message = self._summarize_and_clear_history() + "\n\n" + message
|
|
295
|
-
self.session.conversation_length += get_context_token_count(message)
|
|
296
825
|
|
|
826
|
+
return message
|
|
827
|
+
|
|
828
|
+
def _invoke_model(self, message: str) -> str:
|
|
829
|
+
"""实际调用模型获取响应"""
|
|
297
830
|
if not self.model:
|
|
298
831
|
raise RuntimeError("Model not initialized")
|
|
832
|
+
|
|
833
|
+
# 事件:模型调用前
|
|
834
|
+
try:
|
|
835
|
+
self.event_bus.emit(
|
|
836
|
+
BEFORE_MODEL_CALL,
|
|
837
|
+
agent=self,
|
|
838
|
+
message=message,
|
|
839
|
+
)
|
|
840
|
+
except Exception:
|
|
841
|
+
pass
|
|
842
|
+
|
|
299
843
|
response = self.model.chat_until_success(message) # type: ignore
|
|
844
|
+
# 防御: 模型可能返回空响应(None或空字符串),统一为空字符串并告警
|
|
845
|
+
if not response:
|
|
846
|
+
try:
|
|
847
|
+
PrettyOutput.print("模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
|
848
|
+
except Exception:
|
|
849
|
+
pass
|
|
850
|
+
response = ""
|
|
851
|
+
|
|
852
|
+
# 事件:模型调用后
|
|
853
|
+
try:
|
|
854
|
+
self.event_bus.emit(
|
|
855
|
+
AFTER_MODEL_CALL,
|
|
856
|
+
agent=self,
|
|
857
|
+
message=message,
|
|
858
|
+
response=response,
|
|
859
|
+
)
|
|
860
|
+
except Exception:
|
|
861
|
+
pass
|
|
862
|
+
|
|
300
863
|
self.session.conversation_length += get_context_token_count(response)
|
|
301
864
|
|
|
302
865
|
return response
|
|
@@ -310,28 +873,40 @@ class Agent:
|
|
|
310
873
|
注意:
|
|
311
874
|
仅生成摘要,不修改对话状态
|
|
312
875
|
"""
|
|
313
|
-
|
|
876
|
+
|
|
314
877
|
try:
|
|
315
878
|
if not self.model:
|
|
316
879
|
raise RuntimeError("Model not initialized")
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
)
|
|
320
|
-
|
|
880
|
+
# 优先使用外部传入的 summary_prompt;如为空则回退到默认的会话摘要请求
|
|
881
|
+
safe_summary_prompt = self.summary_prompt or ""
|
|
882
|
+
if isinstance(safe_summary_prompt, str) and safe_summary_prompt.strip() != "":
|
|
883
|
+
prompt_to_use = safe_summary_prompt
|
|
884
|
+
else:
|
|
885
|
+
prompt_to_use = self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
|
|
886
|
+
|
|
887
|
+
summary = self.model.chat_until_success(prompt_to_use) # type: ignore
|
|
888
|
+
# 防御: 可能返回空响应(None或空字符串),统一为空字符串并告警
|
|
889
|
+
if not summary:
|
|
890
|
+
try:
|
|
891
|
+
PrettyOutput.print("总结模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
|
892
|
+
except Exception:
|
|
893
|
+
pass
|
|
894
|
+
summary = ""
|
|
321
895
|
return summary
|
|
322
|
-
except Exception
|
|
323
|
-
print("
|
|
896
|
+
except Exception:
|
|
897
|
+
PrettyOutput.print("总结对话历史失败", OutputType.ERROR)
|
|
324
898
|
return ""
|
|
325
899
|
|
|
326
900
|
def _summarize_and_clear_history(self) -> str:
|
|
327
901
|
"""总结当前对话并清理历史记录
|
|
328
902
|
|
|
329
903
|
该方法将:
|
|
330
|
-
1.
|
|
331
|
-
2.
|
|
332
|
-
3.
|
|
333
|
-
4.
|
|
334
|
-
5.
|
|
904
|
+
1. 提示用户保存重要记忆
|
|
905
|
+
2. 调用 generate_summary 生成摘要
|
|
906
|
+
3. 清除对话历史
|
|
907
|
+
4. 保留系统消息
|
|
908
|
+
5. 添加摘要作为新上下文
|
|
909
|
+
6. 重置对话长度计数器
|
|
335
910
|
|
|
336
911
|
返回:
|
|
337
912
|
str: 包含对话摘要的字符串
|
|
@@ -339,25 +914,72 @@ class Agent:
|
|
|
339
914
|
注意:
|
|
340
915
|
当上下文长度超过最大值时使用
|
|
341
916
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if need_summary:
|
|
348
|
-
summary = self.generate_summary()
|
|
349
|
-
else:
|
|
350
|
-
import tempfile
|
|
917
|
+
# 在清理历史之前,提示用户保存重要记忆(事件驱动触发实际保存)
|
|
918
|
+
if self.force_save_memory:
|
|
919
|
+
PrettyOutput.print(
|
|
920
|
+
"对话历史即将被总结和清理,请先保存重要信息...", OutputType.INFO
|
|
921
|
+
)
|
|
351
922
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
923
|
+
if self._should_use_file_upload():
|
|
924
|
+
return self._handle_history_with_file_upload()
|
|
925
|
+
else:
|
|
926
|
+
return self._handle_history_with_summary()
|
|
927
|
+
|
|
928
|
+
def _should_use_file_upload(self) -> bool:
|
|
929
|
+
"""判断是否应该使用文件上传方式处理历史"""
|
|
930
|
+
return bool(self.model and self.model.support_upload_files())
|
|
931
|
+
|
|
932
|
+
def _handle_history_with_summary(self) -> str:
|
|
933
|
+
"""使用摘要方式处理历史"""
|
|
934
|
+
summary = self.generate_summary()
|
|
935
|
+
|
|
936
|
+
# 先获取格式化的摘要消息
|
|
937
|
+
formatted_summary = ""
|
|
938
|
+
if summary:
|
|
939
|
+
formatted_summary = self._format_summary_message(summary)
|
|
940
|
+
|
|
941
|
+
# 清理历史(但不清理prompt,因为prompt会在builtin_input_handler中设置)
|
|
942
|
+
if self.model:
|
|
943
|
+
# 广播清理历史前事件
|
|
944
|
+
try:
|
|
945
|
+
self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
|
|
946
|
+
except Exception:
|
|
947
|
+
pass
|
|
948
|
+
self.model.reset()
|
|
949
|
+
# 重置后重新设置系统提示词,确保系统约束仍然生效
|
|
950
|
+
self._setup_system_prompt()
|
|
951
|
+
# 重置会话
|
|
952
|
+
self.session.clear_history()
|
|
953
|
+
# 重置 addon_prompt 跳过轮数计数器
|
|
954
|
+
self._addon_prompt_skip_rounds = 0
|
|
955
|
+
# 广播清理历史后的事件
|
|
956
|
+
try:
|
|
957
|
+
self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
|
|
958
|
+
except Exception:
|
|
959
|
+
pass
|
|
355
960
|
|
|
356
|
-
|
|
357
|
-
if not summary:
|
|
358
|
-
return ""
|
|
961
|
+
return formatted_summary
|
|
359
962
|
|
|
360
|
-
|
|
963
|
+
def _handle_history_with_file_upload(self) -> str:
|
|
964
|
+
"""使用文件上传方式处理历史"""
|
|
965
|
+
# 广播清理历史前事件
|
|
966
|
+
try:
|
|
967
|
+
self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
|
|
968
|
+
except Exception:
|
|
969
|
+
pass
|
|
970
|
+
result = self.file_methodology_manager.handle_history_with_file_upload()
|
|
971
|
+
# 重置 addon_prompt 跳过轮数计数器
|
|
972
|
+
self._addon_prompt_skip_rounds = 0
|
|
973
|
+
# 广播清理历史后的事件
|
|
974
|
+
try:
|
|
975
|
+
self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
|
|
976
|
+
except Exception:
|
|
977
|
+
pass
|
|
978
|
+
return result
|
|
979
|
+
|
|
980
|
+
def _format_summary_message(self, summary: str) -> str:
|
|
981
|
+
"""格式化摘要消息"""
|
|
982
|
+
return f"""
|
|
361
983
|
以下是之前对话的关键信息总结:
|
|
362
984
|
|
|
363
985
|
<content>
|
|
@@ -366,14 +988,6 @@ class Agent:
|
|
|
366
988
|
|
|
367
989
|
请基于以上信息继续完成任务。请注意,这是之前对话的摘要,上下文长度已超过限制而被重置。请直接继续任务,无需重复已完成的步骤。如有需要,可以询问用户以获取更多信息。
|
|
368
990
|
"""
|
|
369
|
-
else:
|
|
370
|
-
if self.model and self.model.upload_files([tmp_file_name]):
|
|
371
|
-
return "上传的文件是历史对话信息,请基于历史对话信息继续完成任务。"
|
|
372
|
-
else:
|
|
373
|
-
return ""
|
|
374
|
-
finally:
|
|
375
|
-
if tmp_file_name:
|
|
376
|
-
os.remove(tmp_file_name)
|
|
377
991
|
|
|
378
992
|
def _call_tools(self, response: str) -> Tuple[bool, Any]:
|
|
379
993
|
"""
|
|
@@ -381,7 +995,7 @@ class Agent:
|
|
|
381
995
|
"""
|
|
382
996
|
return execute_tool_call(response, self)
|
|
383
997
|
|
|
384
|
-
def _complete_task(self) -> str:
|
|
998
|
+
def _complete_task(self, auto_completed: bool = False) -> str:
|
|
385
999
|
"""完成任务并生成总结(如果需要)
|
|
386
1000
|
|
|
387
1001
|
返回:
|
|
@@ -392,33 +1006,68 @@ class Agent:
|
|
|
392
1006
|
2. 对于子Agent: 可能会生成总结(如果启用)
|
|
393
1007
|
3. 使用spinner显示生成状态
|
|
394
1008
|
"""
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if not self.model:
|
|
401
|
-
raise RuntimeError("Model not initialized")
|
|
402
|
-
ret = self.model.chat_until_success(self.session.prompt) # type: ignore
|
|
403
|
-
print("✅ 总结生成完成")
|
|
404
|
-
return ret
|
|
1009
|
+
# 事件驱动方式:
|
|
1010
|
+
# - TaskAnalyzer 通过订阅 before_summary/task_completed 事件执行分析与满意度收集
|
|
1011
|
+
# - MemoryManager 通过订阅 before_history_clear/task_completed 事件执行记忆保存(受 force_save_memory 控制)
|
|
1012
|
+
# 为减少耦合,这里不再直接调用上述组件,保持行为由事件触发
|
|
1013
|
+
self._check_and_organize_memory()
|
|
405
1014
|
|
|
406
|
-
|
|
1015
|
+
result = "任务完成"
|
|
407
1016
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
1017
|
+
if self.need_summary:
|
|
1018
|
+
|
|
1019
|
+
# 确保总结提示词非空:若为None或仅空白,则回退到默认提示词
|
|
1020
|
+
safe_summary_prompt = self.summary_prompt or ""
|
|
1021
|
+
if isinstance(safe_summary_prompt, str) and safe_summary_prompt.strip() == "":
|
|
1022
|
+
safe_summary_prompt = DEFAULT_SUMMARY_PROMPT
|
|
1023
|
+
# 注意:不要写回 session.prompt,避免 BEFORE_SUMMARY 事件回调修改/清空后导致使用空prompt
|
|
1024
|
+
# 广播将要生成总结事件
|
|
1025
|
+
try:
|
|
1026
|
+
self.event_bus.emit(
|
|
1027
|
+
BEFORE_SUMMARY,
|
|
1028
|
+
agent=self,
|
|
1029
|
+
prompt=safe_summary_prompt,
|
|
1030
|
+
auto_completed=auto_completed,
|
|
1031
|
+
need_summary=self.need_summary,
|
|
1032
|
+
)
|
|
1033
|
+
except Exception:
|
|
1034
|
+
pass
|
|
413
1035
|
|
|
414
|
-
self.session.prompt = analysis_prompt
|
|
415
1036
|
if not self.model:
|
|
416
1037
|
raise RuntimeError("Model not initialized")
|
|
417
|
-
|
|
418
|
-
self.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
1038
|
+
# 直接使用本地变量,避免受事件回调影响
|
|
1039
|
+
ret = self.model.chat_until_success(safe_summary_prompt) # type: ignore
|
|
1040
|
+
# 防御: 总结阶段模型可能返回空响应(None或空字符串),统一为空字符串并告警
|
|
1041
|
+
if not ret:
|
|
1042
|
+
try:
|
|
1043
|
+
PrettyOutput.print("总结阶段模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
|
1044
|
+
except Exception:
|
|
1045
|
+
pass
|
|
1046
|
+
ret = ""
|
|
1047
|
+
result = ret
|
|
1048
|
+
|
|
1049
|
+
# 广播完成总结事件
|
|
1050
|
+
try:
|
|
1051
|
+
self.event_bus.emit(
|
|
1052
|
+
AFTER_SUMMARY,
|
|
1053
|
+
agent=self,
|
|
1054
|
+
summary=result,
|
|
1055
|
+
)
|
|
1056
|
+
except Exception:
|
|
1057
|
+
pass
|
|
1058
|
+
|
|
1059
|
+
# 广播任务完成事件(不影响主流程)
|
|
1060
|
+
try:
|
|
1061
|
+
self.event_bus.emit(
|
|
1062
|
+
TASK_COMPLETED,
|
|
1063
|
+
agent=self,
|
|
1064
|
+
auto_completed=auto_completed,
|
|
1065
|
+
need_summary=self.need_summary,
|
|
1066
|
+
)
|
|
1067
|
+
except Exception:
|
|
1068
|
+
pass
|
|
1069
|
+
|
|
1070
|
+
return result
|
|
422
1071
|
|
|
423
1072
|
def make_default_addon_prompt(self, need_complete: bool) -> str:
|
|
424
1073
|
"""生成附加提示。
|
|
@@ -427,6 +1076,10 @@ class Agent:
|
|
|
427
1076
|
need_complete: 是否需要完成任务
|
|
428
1077
|
|
|
429
1078
|
"""
|
|
1079
|
+
# 优先使用 PromptManager 以保持逻辑集中
|
|
1080
|
+
if hasattr(self, "prompt_manager"):
|
|
1081
|
+
return self.prompt_manager.build_default_addon_prompt(need_complete)
|
|
1082
|
+
|
|
430
1083
|
# 结构化系统指令
|
|
431
1084
|
action_handlers = ", ".join([handler.name() for handler in self.output_handler])
|
|
432
1085
|
|
|
@@ -437,6 +1090,12 @@ class Agent:
|
|
|
437
1090
|
else ""
|
|
438
1091
|
)
|
|
439
1092
|
|
|
1093
|
+
# 检查工具列表并添加记忆工具相关提示
|
|
1094
|
+
tool_registry = self.get_tool_registry()
|
|
1095
|
+
memory_prompts = self.memory_manager.add_memory_prompts_to_addon(
|
|
1096
|
+
"", tool_registry
|
|
1097
|
+
)
|
|
1098
|
+
|
|
440
1099
|
addon_prompt = f"""
|
|
441
1100
|
<system_prompt>
|
|
442
1101
|
请判断是否已经完成任务,如果已经完成:
|
|
@@ -446,7 +1105,7 @@ class Agent:
|
|
|
446
1105
|
- 仅包含一个操作
|
|
447
1106
|
- 如果信息不明确,请请求用户补充
|
|
448
1107
|
- 如果执行过程中连续失败5次,请使用ask_user询问用户操作
|
|
449
|
-
- 操作列表:{action_handlers}
|
|
1108
|
+
- 操作列表:{action_handlers}{memory_prompts}
|
|
450
1109
|
</system_prompt>
|
|
451
1110
|
|
|
452
1111
|
请继续。
|
|
@@ -469,114 +1128,353 @@ class Agent:
|
|
|
469
1128
|
3. 包含错误处理和恢复逻辑
|
|
470
1129
|
4. 自动加载相关方法论(如果是首次运行)
|
|
471
1130
|
"""
|
|
472
|
-
|
|
473
1131
|
self.session.prompt = f"{user_input}"
|
|
474
1132
|
try:
|
|
475
1133
|
set_agent(self.name, self)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
1134
|
+
# 广播任务开始事件(不影响主流程)
|
|
1135
|
+
try:
|
|
1136
|
+
self.event_bus.emit(
|
|
1137
|
+
TASK_STARTED,
|
|
1138
|
+
agent=self,
|
|
1139
|
+
name=self.name,
|
|
1140
|
+
description=self.description,
|
|
1141
|
+
user_input=self.session.prompt,
|
|
1142
|
+
)
|
|
1143
|
+
except Exception:
|
|
1144
|
+
pass
|
|
1145
|
+
# 如启用规划模式,先判断是否需要拆分并调度子任务
|
|
1146
|
+
if self.plan:
|
|
480
1147
|
try:
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if user_input:
|
|
490
|
-
# 如果有工具调用且用户确认继续,则将干预信息和工具执行结果拼接为prompt
|
|
491
|
-
if any(
|
|
492
|
-
handler.can_handle(current_response)
|
|
493
|
-
for handler in self.output_handler
|
|
494
|
-
):
|
|
495
|
-
if user_confirm(
|
|
496
|
-
"检测到有工具调用,是否继续处理工具调用?", True
|
|
497
|
-
):
|
|
498
|
-
self.session.prompt = (
|
|
499
|
-
f"{user_input}\n\n{current_response}"
|
|
500
|
-
)
|
|
501
|
-
continue
|
|
502
|
-
self.session.prompt += f"{user_input}"
|
|
503
|
-
continue
|
|
504
|
-
|
|
505
|
-
need_return, self.session.prompt = self._call_tools(
|
|
506
|
-
current_response
|
|
507
|
-
)
|
|
1148
|
+
self._maybe_plan_and_dispatch(self.session.prompt)
|
|
1149
|
+
except Exception:
|
|
1150
|
+
# 防御式处理,规划失败不影响主流程
|
|
1151
|
+
pass
|
|
1152
|
+
return self._main_loop()
|
|
1153
|
+
except Exception as e:
|
|
1154
|
+
PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
|
|
1155
|
+
return f"Task failed: {str(e)}"
|
|
508
1156
|
|
|
509
|
-
|
|
510
|
-
|
|
1157
|
+
def _main_loop(self) -> Any:
|
|
1158
|
+
"""主运行循环"""
|
|
1159
|
+
# 委派至独立的运行循环类,保持行为一致
|
|
1160
|
+
loop = AgentRunLoop(self)
|
|
1161
|
+
return loop.run()
|
|
511
1162
|
|
|
512
|
-
|
|
513
|
-
|
|
1163
|
+
def _handle_run_interrupt(self, current_response: str) -> Optional[Union[Any, "LoopAction"]]:
|
|
1164
|
+
"""处理运行中的中断
|
|
514
1165
|
|
|
515
|
-
|
|
516
|
-
|
|
1166
|
+
返回:
|
|
1167
|
+
None: 无中断,或中断后允许继续执行当前响应
|
|
1168
|
+
Any: 需要返回的最终结果
|
|
1169
|
+
LoopAction.SKIP_TURN: 中断后需要跳过当前响应,并立即开始下一次循环
|
|
1170
|
+
"""
|
|
1171
|
+
if not get_interrupt():
|
|
1172
|
+
return None
|
|
517
1173
|
|
|
518
|
-
|
|
519
|
-
|
|
1174
|
+
set_interrupt(False)
|
|
1175
|
+
user_input = self._multiline_input(
|
|
1176
|
+
"模型交互期间被中断,请输入用户干预信息:", False
|
|
1177
|
+
)
|
|
1178
|
+
# 广播中断事件(包含用户输入,可能为空字符串)
|
|
1179
|
+
try:
|
|
1180
|
+
self.event_bus.emit(
|
|
1181
|
+
INTERRUPT_TRIGGERED,
|
|
1182
|
+
agent=self,
|
|
1183
|
+
current_response=current_response,
|
|
1184
|
+
user_input=user_input,
|
|
1185
|
+
)
|
|
1186
|
+
except Exception:
|
|
1187
|
+
pass
|
|
1188
|
+
|
|
1189
|
+
self.run_input_handlers_next_turn = True
|
|
1190
|
+
|
|
1191
|
+
if not user_input:
|
|
1192
|
+
# 用户输入为空,完成任务
|
|
1193
|
+
return self._complete_task(auto_completed=False)
|
|
1194
|
+
|
|
1195
|
+
if any(handler.can_handle(current_response) for handler in self.output_handler):
|
|
1196
|
+
if self.confirm_callback("检测到有工具调用,是否继续处理工具调用?", True):
|
|
1197
|
+
self.session.prompt = join_prompts([
|
|
1198
|
+
f"被用户中断,用户补充信息为:{user_input}",
|
|
1199
|
+
"用户同意继续工具调用。"
|
|
1200
|
+
])
|
|
1201
|
+
return None # 继续执行工具调用
|
|
1202
|
+
else:
|
|
1203
|
+
self.session.prompt = join_prompts([
|
|
1204
|
+
f"被用户中断,用户补充信息为:{user_input}",
|
|
1205
|
+
"检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
|
1206
|
+
])
|
|
1207
|
+
return LoopAction.SKIP_TURN # 请求主循环 continue
|
|
1208
|
+
else:
|
|
1209
|
+
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}"
|
|
1210
|
+
return LoopAction.SKIP_TURN # 请求主循环 continue
|
|
520
1211
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
f"{self.name}: 请输入,或输入空行来结束当前任务:"
|
|
524
|
-
)
|
|
1212
|
+
def _get_next_user_action(self) -> Union[str, "LoopAction"]:
|
|
1213
|
+
"""获取用户下一步操作
|
|
525
1214
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1215
|
+
返回:
|
|
1216
|
+
LoopAction.CONTINUE 或 LoopAction.COMPLETE(兼容旧字符串值 "continue"/"complete")
|
|
1217
|
+
"""
|
|
1218
|
+
user_input = self._multiline_input(
|
|
1219
|
+
f"{self.name}: 请输入,或输入空行来结束当前任务:", False
|
|
1220
|
+
)
|
|
529
1221
|
|
|
530
|
-
|
|
531
|
-
|
|
1222
|
+
if user_input:
|
|
1223
|
+
self.session.prompt = user_input
|
|
1224
|
+
# 使用显式动作信号,保留返回类型注释以保持兼容
|
|
1225
|
+
return LoopAction.CONTINUE # type: ignore[return-value]
|
|
1226
|
+
else:
|
|
1227
|
+
return LoopAction.COMPLETE # type: ignore[return-value]
|
|
532
1228
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1229
|
+
def _first_run(self):
|
|
1230
|
+
"""首次运行初始化"""
|
|
1231
|
+
# 如果工具过多,使用AI进行筛选
|
|
1232
|
+
if self.session.prompt:
|
|
1233
|
+
self._filter_tools_if_needed(self.session.prompt)
|
|
536
1234
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return f"Task failed: {str(e)}"
|
|
1235
|
+
# 准备记忆标签提示
|
|
1236
|
+
memory_tags_prompt = self.memory_manager.prepare_memory_tags_prompt()
|
|
540
1237
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
PrettyOutput.print(
|
|
548
|
-
"文件上传失败,将忽略文件列表", OutputType.WARNING
|
|
549
|
-
)
|
|
550
|
-
# 上传失败则回退到本地加载
|
|
551
|
-
msg = self.session.prompt
|
|
552
|
-
for handler in self.input_handler:
|
|
553
|
-
msg, _ = handler(msg, self)
|
|
554
|
-
self.session.prompt = f"{self.session.prompt}\n\n以下是历史类似问题的执行经验,可参考:\n{load_methodology(msg, self.get_tool_registry())}"
|
|
555
|
-
else:
|
|
556
|
-
if self.files:
|
|
557
|
-
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息和方法论文件,可以从中获取一些经验信息。"
|
|
558
|
-
else:
|
|
559
|
-
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息,可以从中获取一些经验信息。"
|
|
560
|
-
elif self.files:
|
|
561
|
-
if not self.model.upload_files(self.files):
|
|
562
|
-
PrettyOutput.print(
|
|
563
|
-
"文件上传失败,将忽略文件列表", OutputType.WARNING
|
|
564
|
-
)
|
|
565
|
-
else:
|
|
566
|
-
self.session.prompt = f"{self.session.prompt}\n\n上传的文件包含历史对话信息,可以从中获取一些经验信息。"
|
|
567
|
-
else:
|
|
568
|
-
if self.files:
|
|
569
|
-
PrettyOutput.print("不支持上传文件,将忽略文件列表", OutputType.WARNING)
|
|
570
|
-
if self.use_methodology:
|
|
571
|
-
msg = self.session.prompt
|
|
572
|
-
for handler in self.input_handler:
|
|
573
|
-
msg, _ = handler(msg, self)
|
|
574
|
-
self.session.prompt = f"{self.session.prompt}\n\n以下是历史类似问题的执行经验,可参考:\n{load_methodology(msg, self.get_tool_registry())}"
|
|
1238
|
+
# 处理文件上传和方法论加载
|
|
1239
|
+
self.file_methodology_manager.handle_files_and_methodology()
|
|
1240
|
+
|
|
1241
|
+
# 添加记忆标签提示
|
|
1242
|
+
if memory_tags_prompt:
|
|
1243
|
+
self.session.prompt = f"{self.session.prompt}{memory_tags_prompt}"
|
|
575
1244
|
|
|
576
1245
|
self.first = False
|
|
577
1246
|
|
|
578
|
-
def
|
|
1247
|
+
def _create_temp_model(self, system_prompt: str) -> BasePlatform:
|
|
1248
|
+
"""创建一个用于执行一次性任务的临时模型实例,以避免污染主会话。"""
|
|
1249
|
+
temp_model = PlatformRegistry().create_platform(
|
|
1250
|
+
self.model.platform_name() # type: ignore
|
|
1251
|
+
)
|
|
1252
|
+
if not temp_model:
|
|
1253
|
+
raise RuntimeError("创建临时模型失败。")
|
|
1254
|
+
|
|
1255
|
+
temp_model.set_model_name(self.model.name()) # type: ignore
|
|
1256
|
+
temp_model.set_system_prompt(system_prompt)
|
|
1257
|
+
return temp_model
|
|
1258
|
+
|
|
1259
|
+
def _build_child_agent_params(self, name: str, description: str) -> Dict[str, Any]:
|
|
1260
|
+
"""构建子Agent参数,尽量继承父Agent配置,并确保子Agent非交互自动完成。"""
|
|
1261
|
+
use_tools_param: Optional[List[str]] = None
|
|
1262
|
+
try:
|
|
1263
|
+
tr = self.get_tool_registry()
|
|
1264
|
+
if isinstance(tr, ToolRegistry):
|
|
1265
|
+
selected_tools = tr.get_all_tools()
|
|
1266
|
+
use_tools_param = [t["name"] for t in selected_tools]
|
|
1267
|
+
except Exception:
|
|
1268
|
+
use_tools_param = None
|
|
1269
|
+
|
|
1270
|
+
return {
|
|
1271
|
+
"system_prompt": origin_agent_system_prompt,
|
|
1272
|
+
"name": name,
|
|
1273
|
+
"description": description,
|
|
1274
|
+
"model_group": self.model_group,
|
|
1275
|
+
"summary_prompt": self.summary_prompt,
|
|
1276
|
+
"auto_complete": True,
|
|
1277
|
+
"use_tools": use_tools_param,
|
|
1278
|
+
"execute_tool_confirm": self.execute_tool_confirm,
|
|
1279
|
+
"need_summary": self.need_summary,
|
|
1280
|
+
"auto_summary_rounds": self.auto_summary_rounds,
|
|
1281
|
+
"multiline_inputer": self.multiline_inputer,
|
|
1282
|
+
"use_methodology": self.use_methodology,
|
|
1283
|
+
"use_analysis": self.use_analysis,
|
|
1284
|
+
"force_save_memory": self.force_save_memory,
|
|
1285
|
+
"disable_file_edit": self.disable_file_edit,
|
|
1286
|
+
"files": self.files,
|
|
1287
|
+
"confirm_callback": self.confirm_callback,
|
|
1288
|
+
"non_interactive": True,
|
|
1289
|
+
"in_multi_agent": True,
|
|
1290
|
+
"plan": self.plan, # 继承父Agent的规划开关
|
|
1291
|
+
"plan_depth": self.plan_depth + 1, # 子Agent层数+1
|
|
1292
|
+
"plan_max_depth": self.plan_max_depth, # 继承上限
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
def _maybe_plan_and_dispatch(self, task_text: str) -> None:
|
|
1296
|
+
"""委托给 TaskPlanner 执行任务规划与子任务调度,保持向后兼容。"""
|
|
1297
|
+
try:
|
|
1298
|
+
if hasattr(self, "task_planner") and self.task_planner:
|
|
1299
|
+
# 优先使用初始化时注入的规划器
|
|
1300
|
+
self.task_planner.maybe_plan_and_dispatch(task_text) # type: ignore[attr-defined]
|
|
1301
|
+
else:
|
|
1302
|
+
# 防御式回退:临时创建规划器以避免因未初始化导致的崩溃
|
|
1303
|
+
from jarvis.jarvis_agent.task_planner import TaskPlanner
|
|
1304
|
+
TaskPlanner(self, plan_depth=self.plan_depth, plan_max_depth=self.plan_max_depth).maybe_plan_and_dispatch(task_text)
|
|
1305
|
+
except Exception:
|
|
1306
|
+
# 规划失败不影响主流程
|
|
1307
|
+
pass
|
|
1308
|
+
|
|
1309
|
+
def _filter_tools_if_needed(self, task: str):
|
|
1310
|
+
"""如果工具数量超过阈值,使用大模型筛选相关工具"""
|
|
1311
|
+
tool_registry = self.get_tool_registry()
|
|
1312
|
+
if not isinstance(tool_registry, ToolRegistry):
|
|
1313
|
+
return
|
|
1314
|
+
|
|
1315
|
+
all_tools = tool_registry.get_all_tools()
|
|
1316
|
+
threshold = get_tool_filter_threshold()
|
|
1317
|
+
if len(all_tools) <= threshold:
|
|
1318
|
+
return
|
|
1319
|
+
|
|
1320
|
+
# 为工具选择构建提示
|
|
1321
|
+
tools_prompt_part = ""
|
|
1322
|
+
tool_names = []
|
|
1323
|
+
for i, tool in enumerate(all_tools, 1):
|
|
1324
|
+
tool_names.append(tool["name"])
|
|
1325
|
+
tools_prompt_part += f"{i}. {tool['name']}: {tool['description']}\n"
|
|
1326
|
+
|
|
1327
|
+
selection_prompt = f"""
|
|
1328
|
+
用户任务是:
|
|
1329
|
+
<task>
|
|
1330
|
+
{task}
|
|
1331
|
+
</task>
|
|
1332
|
+
|
|
1333
|
+
这是一个可用工具的列表:
|
|
1334
|
+
<tools>
|
|
1335
|
+
{tools_prompt_part}
|
|
1336
|
+
</tools>
|
|
1337
|
+
|
|
1338
|
+
请根据用户任务,从列表中选择最相关的工具。
|
|
1339
|
+
请仅返回所选工具的编号,以逗号分隔。例如:1, 5, 12
|
|
1340
|
+
"""
|
|
1341
|
+
PrettyOutput.print(
|
|
1342
|
+
f"工具数量超过{threshold}个,正在使用AI筛选相关工具...", OutputType.INFO
|
|
1343
|
+
)
|
|
1344
|
+
# 广播工具筛选开始事件
|
|
1345
|
+
try:
|
|
1346
|
+
self.event_bus.emit(
|
|
1347
|
+
BEFORE_TOOL_FILTER,
|
|
1348
|
+
agent=self,
|
|
1349
|
+
task=task,
|
|
1350
|
+
total_tools=len(all_tools),
|
|
1351
|
+
threshold=threshold,
|
|
1352
|
+
)
|
|
1353
|
+
except Exception:
|
|
1354
|
+
pass
|
|
1355
|
+
|
|
1356
|
+
# 使用临时模型实例调用模型,以避免污染历史记录
|
|
1357
|
+
try:
|
|
1358
|
+
temp_model = self._create_temp_model("你是一个帮助筛选工具的助手。")
|
|
1359
|
+
selected_tools_str = temp_model.chat_until_success(
|
|
1360
|
+
selection_prompt
|
|
1361
|
+
) # type: ignore
|
|
1362
|
+
|
|
1363
|
+
# 解析响应并筛选工具
|
|
1364
|
+
selected_indices = [
|
|
1365
|
+
int(i.strip()) for i in re.findall(r"\d+", selected_tools_str)
|
|
1366
|
+
]
|
|
1367
|
+
selected_tool_names = [
|
|
1368
|
+
tool_names[i - 1]
|
|
1369
|
+
for i in selected_indices
|
|
1370
|
+
if 0 < i <= len(tool_names)
|
|
1371
|
+
]
|
|
1372
|
+
|
|
1373
|
+
if selected_tool_names:
|
|
1374
|
+
# 移除重复项
|
|
1375
|
+
selected_tool_names = sorted(list(set(selected_tool_names)))
|
|
1376
|
+
tool_registry.use_tools(selected_tool_names)
|
|
1377
|
+
# 使用筛选后的工具列表重新设置系统提示
|
|
1378
|
+
self._setup_system_prompt()
|
|
1379
|
+
PrettyOutput.print(
|
|
1380
|
+
f"已筛选出 {len(selected_tool_names)} 个相关工具: {', '.join(selected_tool_names)}",
|
|
1381
|
+
OutputType.SUCCESS,
|
|
1382
|
+
)
|
|
1383
|
+
# 广播工具筛选事件
|
|
1384
|
+
try:
|
|
1385
|
+
self.event_bus.emit(
|
|
1386
|
+
TOOL_FILTERED,
|
|
1387
|
+
agent=self,
|
|
1388
|
+
task=task,
|
|
1389
|
+
selected_tools=selected_tool_names,
|
|
1390
|
+
total_tools=len(all_tools),
|
|
1391
|
+
threshold=threshold,
|
|
1392
|
+
)
|
|
1393
|
+
except Exception:
|
|
1394
|
+
pass
|
|
1395
|
+
else:
|
|
1396
|
+
PrettyOutput.print(
|
|
1397
|
+
"AI 未能筛选出任何相关工具,将使用所有工具。", OutputType.WARNING
|
|
1398
|
+
)
|
|
1399
|
+
# 广播工具筛选事件(无筛选结果)
|
|
1400
|
+
try:
|
|
1401
|
+
self.event_bus.emit(
|
|
1402
|
+
TOOL_FILTERED,
|
|
1403
|
+
agent=self,
|
|
1404
|
+
task=task,
|
|
1405
|
+
selected_tools=[],
|
|
1406
|
+
total_tools=len(all_tools),
|
|
1407
|
+
threshold=threshold,
|
|
1408
|
+
)
|
|
1409
|
+
except Exception:
|
|
1410
|
+
pass
|
|
1411
|
+
|
|
1412
|
+
except Exception as e:
|
|
1413
|
+
PrettyOutput.print(
|
|
1414
|
+
f"工具筛选失败: {e},将使用所有工具。", OutputType.ERROR
|
|
1415
|
+
)
|
|
1416
|
+
|
|
1417
|
+
def _check_and_organize_memory(self):
|
|
579
1418
|
"""
|
|
580
|
-
|
|
1419
|
+
检查记忆库状态,如果满足条件则提示用户整理。
|
|
1420
|
+
每天只检测一次。
|
|
581
1421
|
"""
|
|
582
|
-
|
|
1422
|
+
try:
|
|
1423
|
+
# 检查项目记忆
|
|
1424
|
+
self._perform_memory_check("project_long_term", Path(".jarvis"), "project")
|
|
1425
|
+
# 检查全局记忆
|
|
1426
|
+
self._perform_memory_check(
|
|
1427
|
+
"global_long_term",
|
|
1428
|
+
Path(get_data_dir()),
|
|
1429
|
+
"global",
|
|
1430
|
+
)
|
|
1431
|
+
except Exception as e:
|
|
1432
|
+
PrettyOutput.print(f"检查记忆库时发生意外错误: {e}", OutputType.WARNING)
|
|
1433
|
+
|
|
1434
|
+
def _perform_memory_check(self, memory_type: str, base_path: Path, scope_name: str):
|
|
1435
|
+
"""执行特定范围的记忆检查和整理"""
|
|
1436
|
+
check_file = base_path / ".last_memory_organizer_check"
|
|
1437
|
+
now = datetime.datetime.now()
|
|
1438
|
+
|
|
1439
|
+
if check_file.exists():
|
|
1440
|
+
try:
|
|
1441
|
+
last_check_time = datetime.datetime.fromisoformat(
|
|
1442
|
+
check_file.read_text()
|
|
1443
|
+
)
|
|
1444
|
+
if (now - last_check_time).total_seconds() < 24 * 3600:
|
|
1445
|
+
return # 24小时内已检查
|
|
1446
|
+
except (ValueError, FileNotFoundError):
|
|
1447
|
+
# 文件内容无效或文件在读取时被删除,继续执行检查
|
|
1448
|
+
pass
|
|
1449
|
+
|
|
1450
|
+
# 立即更新检查时间,防止并发或重复检查
|
|
1451
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
1452
|
+
check_file.write_text(now.isoformat())
|
|
1453
|
+
|
|
1454
|
+
organizer = MemoryOrganizer()
|
|
1455
|
+
# NOTE: 使用受保护方法以避免重复实现逻辑
|
|
1456
|
+
memories = organizer._load_memories(memory_type)
|
|
1457
|
+
|
|
1458
|
+
if len(memories) < 200:
|
|
1459
|
+
return
|
|
1460
|
+
|
|
1461
|
+
# NOTE: 使用受保护方法以避免重复实现逻辑
|
|
1462
|
+
overlap_groups = organizer._find_overlapping_memories(memories, min_overlap=3)
|
|
1463
|
+
has_significant_overlap = any(groups for groups in overlap_groups.values())
|
|
1464
|
+
|
|
1465
|
+
if not has_significant_overlap:
|
|
1466
|
+
return
|
|
1467
|
+
|
|
1468
|
+
prompt = (
|
|
1469
|
+
f"检测到您的 '{scope_name}' 记忆库中包含 {len(memories)} 条记忆,"
|
|
1470
|
+
f"并且存在3个以上标签重叠的记忆。\n"
|
|
1471
|
+
f"是否立即整理记忆库以优化性能和相关性?"
|
|
1472
|
+
)
|
|
1473
|
+
if self.confirm_callback(prompt, True):
|
|
1474
|
+
PrettyOutput.print(
|
|
1475
|
+
f"正在开始整理 '{scope_name}' ({memory_type}) 记忆库...",
|
|
1476
|
+
OutputType.INFO,
|
|
1477
|
+
)
|
|
1478
|
+
organizer.organize_memories(memory_type, min_overlap=3)
|
|
1479
|
+
else:
|
|
1480
|
+
PrettyOutput.print(f"已取消 '{scope_name}' 记忆库整理。", OutputType.INFO)
|