jarvis-ai-assistant 0.4.0__py3-none-any.whl → 0.4.2__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 +95 -74
- jarvis/jarvis_agent/agent_manager.py +6 -4
- jarvis/jarvis_agent/edit_file_handler.py +2 -2
- jarvis/jarvis_agent/jarvis.py +40 -9
- jarvis/jarvis_agent/rewrite_file_handler.py +143 -0
- jarvis/jarvis_agent/run_loop.py +23 -4
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_server.py +332 -234
- jarvis/jarvis_code_agent/code_agent.py +10 -7
- jarvis/jarvis_code_analysis/code_review.py +0 -1
- jarvis/jarvis_data/config_schema.json +10 -0
- jarvis/jarvis_multi_agent/__init__.py +227 -34
- jarvis/jarvis_multi_agent/main.py +10 -1
- jarvis/jarvis_platform/base.py +15 -6
- jarvis/jarvis_tools/registry.py +6 -0
- jarvis/jarvis_tools/sub_agent.py +19 -34
- jarvis/jarvis_tools/sub_code_agent.py +3 -1
- jarvis/jarvis_utils/config.py +26 -11
- jarvis/jarvis_utils/input.py +77 -6
- {jarvis_ai_assistant-0.4.0.dist-info → jarvis_ai_assistant-0.4.2.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.4.0.dist-info → jarvis_ai_assistant-0.4.2.dist-info}/RECORD +26 -28
- jarvis/jarvis_agent/config.py +0 -99
- jarvis/jarvis_tools/edit_file.py +0 -208
- jarvis/jarvis_tools/rewrite_file.py +0 -191
- {jarvis_ai_assistant-0.4.0.dist-info → jarvis_ai_assistant-0.4.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.4.0.dist-info → jarvis_ai_assistant-0.4.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.4.0.dist-info → jarvis_ai_assistant-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.4.0.dist-info → jarvis_ai_assistant-0.4.2.dist-info}/top_level.txt +0 -0
|
@@ -56,9 +56,11 @@ class CodeAgent:
|
|
|
56
56
|
need_summary: bool = True,
|
|
57
57
|
append_tools: Optional[str] = None,
|
|
58
58
|
tool_group: Optional[str] = None,
|
|
59
|
+
non_interactive: Optional[bool] = None,
|
|
59
60
|
):
|
|
60
61
|
self.root_dir = os.getcwd()
|
|
61
62
|
self.tool_group = tool_group
|
|
63
|
+
self.non_interactive = non_interactive
|
|
62
64
|
|
|
63
65
|
# 检测 git username 和 email 是否已设置
|
|
64
66
|
self._check_git_config()
|
|
@@ -68,7 +70,6 @@ class CodeAgent:
|
|
|
68
70
|
"search_web",
|
|
69
71
|
"ask_user",
|
|
70
72
|
"read_code",
|
|
71
|
-
"rewrite_file",
|
|
72
73
|
"save_memory",
|
|
73
74
|
"retrieve_memory",
|
|
74
75
|
"clear_memory",
|
|
@@ -89,11 +90,11 @@ class CodeAgent:
|
|
|
89
90
|
system_prompt=code_system_prompt,
|
|
90
91
|
name="CodeAgent",
|
|
91
92
|
auto_complete=False,
|
|
92
|
-
output_handler=[tool_registry, EditFileHandler()], # type: ignore
|
|
93
93
|
model_group=model_group,
|
|
94
94
|
need_summary=need_summary,
|
|
95
95
|
use_methodology=False, # 禁用方法论
|
|
96
96
|
use_analysis=False, # 禁用分析
|
|
97
|
+
non_interactive=self.non_interactive,
|
|
97
98
|
)
|
|
98
99
|
|
|
99
100
|
self.agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
@@ -137,7 +138,7 @@ class CodeAgent:
|
|
|
137
138
|
|
|
138
139
|
## 文件编辑工具使用规范
|
|
139
140
|
- 对于部分文件内容修改,使用edit_file工具
|
|
140
|
-
- 对于需要重写整个文件内容,使用
|
|
141
|
+
- 对于需要重写整个文件内容,使用 REWRITE 操作
|
|
141
142
|
- 对于简单的修改,可以使用execute_script工具执行shell命令完成
|
|
142
143
|
|
|
143
144
|
## 子任务与子CodeAgent
|
|
@@ -567,8 +568,8 @@ class CodeAgent:
|
|
|
567
568
|
1. 每次响应仅执行一步操作,先分析再修改,避免一步多改。
|
|
568
569
|
2. 充分利用工具理解用户需求和现有代码,禁止凭空假设。
|
|
569
570
|
3. 如果不清楚要修改的文件,必须先分析并找出需要修改的文件,明确目标后再进行编辑。
|
|
570
|
-
4. 代码编辑任务优先使用
|
|
571
|
-
5. 如需大范围重写,才可使用
|
|
571
|
+
4. 代码编辑任务优先使用 PATCH 操作,确保搜索文本在目标文件中有且仅有一次精确匹配,保证修改的准确性和安全性。
|
|
572
|
+
5. 如需大范围重写,才可使用 REWRITE 操作。
|
|
572
573
|
6. 如遇信息不明,优先调用工具补充分析,不要主观臆断。
|
|
573
574
|
"""
|
|
574
575
|
|
|
@@ -851,9 +852,10 @@ def cli(
|
|
|
851
852
|
PrettyOutput.print(
|
|
852
853
|
f"警告:当前目录 '{curr_dir_path}' 不是一个git仓库。", OutputType.WARNING
|
|
853
854
|
)
|
|
854
|
-
if user_confirm(
|
|
855
|
+
init_git = True if non_interactive else user_confirm(
|
|
855
856
|
f"是否要在 '{curr_dir_path}' 中初始化一个新的git仓库?", default=True
|
|
856
|
-
)
|
|
857
|
+
)
|
|
858
|
+
if init_git:
|
|
857
859
|
try:
|
|
858
860
|
subprocess.run(
|
|
859
861
|
["git", "init"],
|
|
@@ -881,6 +883,7 @@ def cli(
|
|
|
881
883
|
need_summary=False,
|
|
882
884
|
append_tools=append_tools,
|
|
883
885
|
tool_group=tool_group,
|
|
886
|
+
non_interactive=non_interactive,
|
|
884
887
|
)
|
|
885
888
|
|
|
886
889
|
# 尝试恢复会话
|
|
@@ -193,6 +193,11 @@
|
|
|
193
193
|
"description": "脚本执行的超时时间(秒),仅在非交互模式下生效。",
|
|
194
194
|
"default": 300
|
|
195
195
|
},
|
|
196
|
+
"JARVIS_AUTO_SUMMARY_ROUNDS": {
|
|
197
|
+
"type": "number",
|
|
198
|
+
"description": "基于对话轮次的自动总结阈值(达到该轮次后自动总结并清理历史)",
|
|
199
|
+
"default": 20
|
|
200
|
+
},
|
|
196
201
|
"JARVIS_CONFIRM_BEFORE_APPLY_PATCH": {
|
|
197
202
|
"type": "boolean",
|
|
198
203
|
"description": "应用补丁前是否需要确认",
|
|
@@ -322,6 +327,11 @@
|
|
|
322
327
|
"description": "是否保存会话记录",
|
|
323
328
|
"default": false
|
|
324
329
|
},
|
|
330
|
+
"JARVIS_SKIP_PREDEFINED_TASKS": {
|
|
331
|
+
"type": "boolean",
|
|
332
|
+
"description": "是否跳过预定义任务加载(不读取 pre-command 列表)",
|
|
333
|
+
"default": false
|
|
334
|
+
},
|
|
325
335
|
"JARVIS_GIT_CHECK_MODE": {
|
|
326
336
|
"type": "string",
|
|
327
337
|
"enum": ["strict", "warn"],
|
|
@@ -7,17 +7,20 @@ import yaml
|
|
|
7
7
|
from jarvis.jarvis_agent import Agent
|
|
8
8
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
9
9
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
10
|
+
from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
|
|
11
|
+
from jarvis.jarvis_agent.rewrite_file_handler import RewriteFileHandler
|
|
10
12
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
11
13
|
from jarvis.jarvis_utils.tag import ct, ot
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class MultiAgent(OutputHandler):
|
|
15
|
-
def __init__(self, agents_config: List[Dict], main_agent_name: str):
|
|
17
|
+
def __init__(self, agents_config: List[Dict], main_agent_name: str, common_system_prompt: str = ""):
|
|
16
18
|
self.agents_config = agents_config
|
|
17
19
|
self.agents_config_map = {c["name"]: c for c in agents_config}
|
|
18
20
|
self.agents: Dict[str, Agent] = {}
|
|
19
21
|
self.main_agent_name = main_agent_name
|
|
20
22
|
self.original_question: Optional[str] = None
|
|
23
|
+
self.common_system_prompt: str = common_system_prompt
|
|
21
24
|
|
|
22
25
|
def prompt(self) -> str:
|
|
23
26
|
return f"""
|
|
@@ -66,18 +69,168 @@ content: |2
|
|
|
66
69
|
"""
|
|
67
70
|
|
|
68
71
|
def can_handle(self, response: str) -> bool:
|
|
69
|
-
|
|
72
|
+
# 只要检测到 SEND_MESSAGE 起始标签即认为可处理,
|
|
73
|
+
# 即便内容有误也由 handle 返回明确错误与修复指导
|
|
74
|
+
return ot("SEND_MESSAGE") in response
|
|
70
75
|
|
|
71
76
|
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
"""
|
|
78
|
+
处理 SEND_MESSAGE。若存在格式/解析/字段/目标等问题,返回明确错误原因与修复指导。
|
|
79
|
+
"""
|
|
80
|
+
# 优先使用解析器获取“正确路径”结果
|
|
81
|
+
parsed = self._extract_send_msg(response)
|
|
82
|
+
if len(parsed) == 1:
|
|
83
|
+
msg = parsed[0]
|
|
84
|
+
# 字段校验
|
|
85
|
+
to_val = msg.get("to")
|
|
86
|
+
content_val = msg.get("content")
|
|
87
|
+
missing = []
|
|
88
|
+
if not to_val:
|
|
89
|
+
missing.append("to")
|
|
90
|
+
if content_val is None or (isinstance(content_val, str) and content_val.strip() == ""):
|
|
91
|
+
# 允许空格/空行被视为缺失
|
|
92
|
+
missing.append("content")
|
|
93
|
+
if missing:
|
|
94
|
+
guidance = (
|
|
95
|
+
"SEND_MESSAGE 字段缺失或为空:"
|
|
96
|
+
+ ", ".join(missing)
|
|
97
|
+
+ "\n修复建议:\n"
|
|
98
|
+
"- 必须包含 to 和 content 字段\n"
|
|
99
|
+
"- to: 目标智能体名称(字符串)\n"
|
|
100
|
+
"- content: 发送内容,建议使用多行块 |2 保持格式\n"
|
|
101
|
+
"示例:\n"
|
|
102
|
+
f"{ot('SEND_MESSAGE')}\n"
|
|
103
|
+
"to: 目标Agent名称\n"
|
|
104
|
+
"content: |2\n"
|
|
105
|
+
" 这里填写要发送的消息内容\n"
|
|
106
|
+
f"{ct('SEND_MESSAGE')}"
|
|
107
|
+
)
|
|
108
|
+
return False, guidance
|
|
109
|
+
# 类型校验
|
|
110
|
+
if not isinstance(to_val, str):
|
|
111
|
+
return False, "SEND_MESSAGE 字段类型错误:to 必须为字符串。修复建议:将 to 改为字符串,如 to: ChapterPolisher"
|
|
112
|
+
if not isinstance(content_val, str):
|
|
113
|
+
return False, "SEND_MESSAGE 字段类型错误:content 必须为字符串。修复建议:将 content 改为字符串或使用多行块 content: |2"
|
|
114
|
+
# 目标校验
|
|
115
|
+
if to_val not in self.agents_config_map:
|
|
116
|
+
available = ", ".join(self.agents_config_map.keys())
|
|
117
|
+
return (
|
|
118
|
+
False,
|
|
119
|
+
f"目标智能体不存在:'{to_val}' 不在可用列表中。\n"
|
|
120
|
+
f"可用智能体:[{available}]\n"
|
|
121
|
+
"修复建议:\n"
|
|
122
|
+
"- 将 to 修改为上述可用智能体之一\n"
|
|
123
|
+
"- 或检查配置中是否遗漏了该智能体的定义"
|
|
124
|
+
)
|
|
125
|
+
# 通过校验,交给上层发送
|
|
126
|
+
return True, {"to": to_val, "content": content_val}
|
|
127
|
+
elif len(parsed) > 1:
|
|
128
|
+
return (
|
|
129
|
+
False,
|
|
130
|
+
"检测到多个 SEND_MESSAGE 块。一次只能发送一个消息。\n修复建议:合并消息或分多轮发送,每轮仅保留一个 SEND_MESSAGE 块。"
|
|
131
|
+
)
|
|
132
|
+
# 未成功解析,进行诊断并返回可操作指导
|
|
133
|
+
try:
|
|
134
|
+
normalized = response.replace("\r\n", "\n").replace("\r", "\n")
|
|
135
|
+
except Exception:
|
|
136
|
+
normalized = response
|
|
137
|
+
ot_tag = ot("SEND_MESSAGE")
|
|
138
|
+
ct_tag = ct("SEND_MESSAGE")
|
|
139
|
+
has_open = ot_tag in normalized
|
|
140
|
+
has_close = ct_tag in normalized
|
|
141
|
+
if has_open and not has_close:
|
|
142
|
+
return (
|
|
143
|
+
False,
|
|
144
|
+
f"检测到 {ot_tag} 但缺少结束标签 {ct_tag}。\n"
|
|
145
|
+
"修复建议:在消息末尾补充结束标签,并确认标签各自独占一行。\n"
|
|
146
|
+
"示例:\n"
|
|
147
|
+
f"{ot_tag}\n"
|
|
148
|
+
"to: 目标Agent名称\n"
|
|
149
|
+
"content: |2\n"
|
|
150
|
+
" 这里填写要发送的消息内容\n"
|
|
151
|
+
f"{ct_tag}"
|
|
152
|
+
)
|
|
153
|
+
# 尝试提取原始块并指出 YAML 问题
|
|
154
|
+
import re as _re
|
|
155
|
+
pattern = _re.compile(
|
|
156
|
+
rf"{_re.escape(ot_tag)}[ \t]*\n(.*?)(?:\n)?[ \t]*{_re.escape(ct_tag)}",
|
|
157
|
+
_re.DOTALL,
|
|
158
|
+
)
|
|
159
|
+
blocks = pattern.findall(normalized)
|
|
160
|
+
if not blocks:
|
|
161
|
+
alt_pattern = _re.compile(
|
|
162
|
+
rf"{_re.escape(ot_tag)}[ \t]*(.*?)[ \t]*{_re.escape(ct_tag)}",
|
|
163
|
+
_re.DOTALL,
|
|
164
|
+
)
|
|
165
|
+
blocks = alt_pattern.findall(normalized)
|
|
166
|
+
if not blocks:
|
|
74
167
|
return (
|
|
75
168
|
False,
|
|
76
|
-
"
|
|
169
|
+
"SEND_MESSAGE 格式错误:未能识别完整的消息块。\n"
|
|
170
|
+
"修复建议:确保起止标签在单独行上,且中间内容为合法的 YAML,包含 to 与 content 字段。"
|
|
171
|
+
)
|
|
172
|
+
raw = blocks[0]
|
|
173
|
+
try:
|
|
174
|
+
msg_obj = yaml.safe_load(raw)
|
|
175
|
+
if not isinstance(msg_obj, dict):
|
|
176
|
+
return (
|
|
177
|
+
False,
|
|
178
|
+
"SEND_MESSAGE 内容必须为 YAML 对象(键值对)。\n"
|
|
179
|
+
"修复建议:使用 to 与 content 字段构成的对象。\n"
|
|
180
|
+
"示例:\n"
|
|
181
|
+
f"{ot('SEND_MESSAGE')}\n"
|
|
182
|
+
"to: 目标Agent名称\n"
|
|
183
|
+
"content: |2\n"
|
|
184
|
+
" 这里填写要发送的消息内容\n"
|
|
185
|
+
f"{ct('SEND_MESSAGE')}"
|
|
186
|
+
)
|
|
187
|
+
missing_keys = [k for k in ("to", "content") if k not in msg_obj]
|
|
188
|
+
if missing_keys:
|
|
189
|
+
return (
|
|
190
|
+
False,
|
|
191
|
+
"SEND_MESSAGE 缺少必要字段:" + ", ".join(missing_keys) + "\n"
|
|
192
|
+
"修复建议:补充缺失字段。\n"
|
|
193
|
+
"示例:\n"
|
|
194
|
+
f"{ot('SEND_MESSAGE')}\n"
|
|
195
|
+
"to: 目标Agent名称\n"
|
|
196
|
+
"content: |2\n"
|
|
197
|
+
" 这里填写要发送的消息内容\n"
|
|
198
|
+
f"{ct('SEND_MESSAGE')}"
|
|
199
|
+
)
|
|
200
|
+
# 针对值类型的提示(更细)
|
|
201
|
+
if not isinstance(msg_obj.get("to"), str):
|
|
202
|
+
return False, "SEND_MESSAGE 字段类型错误:to 必须为字符串。"
|
|
203
|
+
if not isinstance(msg_obj.get("content"), str):
|
|
204
|
+
return False, "SEND_MESSAGE 字段类型错误:content 必须为字符串,建议使用多行块 |2。"
|
|
205
|
+
# 若到此仍未返回,说明结构基本正确,但 _extract_send_msg 未命中,给出泛化建议
|
|
206
|
+
return (
|
|
207
|
+
False,
|
|
208
|
+
"SEND_MESSAGE 格式可能存在缩进或空白字符问题,导致未被系统识别。\n"
|
|
209
|
+
"修复建议:\n"
|
|
210
|
+
"- 确保起止标签各占一行\n"
|
|
211
|
+
"- 标签与内容之间保留换行\n"
|
|
212
|
+
"- 使用 content: |2 并保证 YAML 缩进一致\n"
|
|
213
|
+
"示例:\n"
|
|
214
|
+
f"{ot('SEND_MESSAGE')}\n"
|
|
215
|
+
"to: 目标Agent名称\n"
|
|
216
|
+
"content: |2\n"
|
|
217
|
+
" 这里填写要发送的消息内容\n"
|
|
218
|
+
f"{ct('SEND_MESSAGE')}"
|
|
219
|
+
)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
return (
|
|
222
|
+
False,
|
|
223
|
+
f"SEND_MESSAGE YAML 解析失败:{str(e)}\n"
|
|
224
|
+
"修复建议:\n"
|
|
225
|
+
"- 检查冒号、缩进与引号是否正确\n"
|
|
226
|
+
"- 使用 content: |2 多行块以避免缩进歧义\n"
|
|
227
|
+
"示例:\n"
|
|
228
|
+
f"{ot('SEND_MESSAGE')}\n"
|
|
229
|
+
"to: 目标Agent名称\n"
|
|
230
|
+
"content: |2\n"
|
|
231
|
+
" 这里填写要发送的消息内容\n"
|
|
232
|
+
f"{ct('SEND_MESSAGE')}"
|
|
77
233
|
)
|
|
78
|
-
if len(send_messages) == 0:
|
|
79
|
-
return False, ""
|
|
80
|
-
return True, send_messages[0]
|
|
81
234
|
|
|
82
235
|
def name(self) -> str:
|
|
83
236
|
return "SEND_MESSAGE"
|
|
@@ -89,16 +242,38 @@ content: |2
|
|
|
89
242
|
Args:
|
|
90
243
|
content: The content containing send message
|
|
91
244
|
"""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
245
|
+
# Normalize line endings to handle CRLF/CR cases to ensure robust matching
|
|
246
|
+
try:
|
|
247
|
+
normalized = content.replace("\r\n", "\n").replace("\r", "\n")
|
|
248
|
+
except Exception:
|
|
249
|
+
normalized = content
|
|
250
|
+
|
|
251
|
+
ot_tag = ot("SEND_MESSAGE")
|
|
252
|
+
ct_tag = ct("SEND_MESSAGE")
|
|
253
|
+
|
|
254
|
+
# Auto-append closing tag if missing
|
|
255
|
+
if ot_tag in normalized and ct_tag not in normalized:
|
|
256
|
+
normalized += "\n" + ct_tag
|
|
257
|
+
|
|
258
|
+
# Use robust regex with DOTALL; escape tags to avoid regex meta issues
|
|
259
|
+
pattern = re.compile(
|
|
260
|
+
rf"{re.escape(ot_tag)}[ \t]*\n(.*?)(?:\n)?[ \t]*{re.escape(ct_tag)}",
|
|
261
|
+
re.DOTALL,
|
|
96
262
|
)
|
|
263
|
+
data = pattern.findall(normalized)
|
|
264
|
+
# Fallback: handle cases without explicit newlines around closing tag
|
|
265
|
+
if not data:
|
|
266
|
+
alt_pattern = re.compile(
|
|
267
|
+
rf"{re.escape(ot_tag)}[ \t]*(.*?)[ \t]*{re.escape(ct_tag)}",
|
|
268
|
+
re.DOTALL,
|
|
269
|
+
)
|
|
270
|
+
data = alt_pattern.findall(normalized)
|
|
271
|
+
|
|
97
272
|
ret = []
|
|
98
273
|
for item in data:
|
|
99
274
|
try:
|
|
100
275
|
msg = yaml.safe_load(item)
|
|
101
|
-
if "to" in msg and "content" in msg:
|
|
276
|
+
if isinstance(msg, dict) and "to" in msg and "content" in msg:
|
|
102
277
|
ret.append(msg)
|
|
103
278
|
except Exception:
|
|
104
279
|
continue
|
|
@@ -112,6 +287,20 @@ content: |2
|
|
|
112
287
|
return None
|
|
113
288
|
|
|
114
289
|
config = self.agents_config_map[name].copy()
|
|
290
|
+
# 标记为多智能体运行,避免在非交互模式下自动开启 auto_complete
|
|
291
|
+
config.setdefault("in_multi_agent", True)
|
|
292
|
+
# 非主智能体统一禁用自动补全,防止多智能体并行时误触发自动交互
|
|
293
|
+
if name != self.main_agent_name:
|
|
294
|
+
config["auto_complete"] = False
|
|
295
|
+
|
|
296
|
+
# Prepend common system prompt if configured
|
|
297
|
+
common_sp = getattr(self, "common_system_prompt", "")
|
|
298
|
+
if common_sp:
|
|
299
|
+
existing_sp = config.get("system_prompt", "")
|
|
300
|
+
if existing_sp:
|
|
301
|
+
config["system_prompt"] = f"{common_sp}\n\n{existing_sp}"
|
|
302
|
+
else:
|
|
303
|
+
config["system_prompt"] = common_sp
|
|
115
304
|
|
|
116
305
|
if name != self.main_agent_name and self.original_question:
|
|
117
306
|
system_prompt = config.get("system_prompt", "")
|
|
@@ -119,18 +308,7 @@ content: |2
|
|
|
119
308
|
f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
|
|
120
309
|
)
|
|
121
310
|
|
|
122
|
-
|
|
123
|
-
if len(output_handler) == 0:
|
|
124
|
-
output_handler = [
|
|
125
|
-
ToolRegistry(),
|
|
126
|
-
self,
|
|
127
|
-
]
|
|
128
|
-
else:
|
|
129
|
-
if not any(isinstance(h, MultiAgent) for h in output_handler):
|
|
130
|
-
output_handler.append(self)
|
|
131
|
-
config["output_handler"] = output_handler
|
|
132
|
-
|
|
133
|
-
agent = Agent(**config)
|
|
311
|
+
agent = Agent(output_handler=[ToolRegistry(), EditFileHandler(), RewriteFileHandler(), self],**config)
|
|
134
312
|
self.agents[name] = agent
|
|
135
313
|
return agent
|
|
136
314
|
|
|
@@ -155,9 +333,14 @@ content: |2
|
|
|
155
333
|
break
|
|
156
334
|
|
|
157
335
|
# Generate a brief summary via direct model call to avoid run-loop recursion
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
336
|
+
# 如果在配置中显式设置了 summary_on_send=False,则不生成摘要
|
|
337
|
+
sender_config = self.agents_config_map.get(last_agent_name, {}) if hasattr(self, "agents_config_map") else {}
|
|
338
|
+
summary_on_send = sender_config.get("summary_on_send", True)
|
|
339
|
+
summary_text = ""
|
|
340
|
+
if summary_on_send:
|
|
341
|
+
try:
|
|
342
|
+
# 参照 Agent.generate_summary 的实现思路:基于当前 session.prompt 追加请求提示,直接调用底层模型
|
|
343
|
+
multi_agent_summary_prompt = """
|
|
161
344
|
请基于当前会话,为即将发送给其他智能体的协作交接写一段摘要,包含:
|
|
162
345
|
- 已完成的主要工作与产出
|
|
163
346
|
- 关键决策及其理由
|
|
@@ -168,12 +351,12 @@ content: |2
|
|
|
168
351
|
- 仅输出纯文本,不包含任何指令或工具调用
|
|
169
352
|
- 使用简洁的要点式表述
|
|
170
353
|
""".strip()
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
354
|
+
summary_any: Any = agent.model.chat_until_success( # type: ignore[attr-defined]
|
|
355
|
+
f"{agent.session.prompt}\n{multi_agent_summary_prompt}"
|
|
356
|
+
)
|
|
357
|
+
summary_text = summary_any.strip() if isinstance(summary_any, str) else ""
|
|
358
|
+
except Exception:
|
|
359
|
+
summary_text = ""
|
|
177
360
|
prompt = f"""
|
|
178
361
|
Please handle this message:
|
|
179
362
|
from: {last_agent_name}
|
|
@@ -200,10 +383,20 @@ content: {msg['content']}
|
|
|
200
383
|
f"{last_agent_name} 正在向 {to_agent_name} 发送消息...", OutputType.INFO
|
|
201
384
|
)
|
|
202
385
|
|
|
386
|
+
# Keep a reference to the sender before switching to the receiver
|
|
387
|
+
sender_agent = agent
|
|
388
|
+
|
|
203
389
|
agent = self._get_agent(to_agent_name)
|
|
204
390
|
if not agent:
|
|
205
391
|
return f"智能体 {to_agent_name} 未找到"
|
|
206
392
|
|
|
393
|
+
# Check if the sending agent should be cleared
|
|
394
|
+
sender_config = self.agents_config_map.get(last_agent_name, {})
|
|
395
|
+
if sender_config.get("clear_after_send_message"):
|
|
396
|
+
if sender_agent:
|
|
397
|
+
PrettyOutput.print(f"清除智能体 {last_agent_name} 在发送消息后的历史记录...", OutputType.INFO)
|
|
398
|
+
sender_agent.clear_history()
|
|
399
|
+
|
|
207
400
|
last_agent_name = agent.name
|
|
208
401
|
msg = agent.run(prompt)
|
|
209
402
|
return ""
|
|
@@ -20,6 +20,9 @@ def cli(
|
|
|
20
20
|
user_input: Optional[str] = typer.Option(
|
|
21
21
|
None, "--input", "-i", help="用户输入(可选)"
|
|
22
22
|
),
|
|
23
|
+
model_group: Optional[str] = typer.Option(
|
|
24
|
+
None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
|
|
25
|
+
),
|
|
23
26
|
non_interactive: bool = typer.Option(
|
|
24
27
|
False, "-n", "--non-interactive", help="启用非交互模式:用户无法与命令交互,脚本执行超时限制为5分钟"
|
|
25
28
|
),
|
|
@@ -45,6 +48,8 @@ def cli(
|
|
|
45
48
|
try:
|
|
46
49
|
if non_interactive:
|
|
47
50
|
set_config("JARVIS_NON_INTERACTIVE", True)
|
|
51
|
+
if model_group:
|
|
52
|
+
set_config("JARVIS_LLM_GROUP", str(model_group))
|
|
48
53
|
except Exception:
|
|
49
54
|
# 静默忽略同步异常,不影响主流程
|
|
50
55
|
pass
|
|
@@ -61,7 +66,11 @@ def cli(
|
|
|
61
66
|
raise ValueError("必须指定main_agent作为主智能体")
|
|
62
67
|
|
|
63
68
|
# 创建并运行多智能体系统
|
|
64
|
-
multi_agent = MultiAgent(
|
|
69
|
+
multi_agent = MultiAgent(
|
|
70
|
+
agents_config,
|
|
71
|
+
main_agent_name,
|
|
72
|
+
common_system_prompt=str(config_data.get("common_system_prompt", "") or "")
|
|
73
|
+
)
|
|
65
74
|
final_input = (
|
|
66
75
|
user_input
|
|
67
76
|
if user_input is not None
|
jarvis/jarvis_platform/base.py
CHANGED
|
@@ -24,6 +24,7 @@ from jarvis.jarvis_utils.config import (
|
|
|
24
24
|
)
|
|
25
25
|
from jarvis.jarvis_utils.embedding import split_text_into_chunks
|
|
26
26
|
from jarvis.jarvis_utils.globals import set_in_chat, get_interrupt, console
|
|
27
|
+
import jarvis.jarvis_utils.globals as G
|
|
27
28
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
28
29
|
from jarvis.jarvis_utils.tag import ct, ot
|
|
29
30
|
from jarvis.jarvis_utils.utils import get_context_token_count, while_success, while_true
|
|
@@ -83,6 +84,11 @@ class BasePlatform(ABC):
|
|
|
83
84
|
|
|
84
85
|
start_time = time.time()
|
|
85
86
|
|
|
87
|
+
# 当输入为空白字符串时,打印警告并直接返回空字符串
|
|
88
|
+
if message.strip() == "":
|
|
89
|
+
PrettyOutput.print("输入为空白字符串,已忽略本次请求", OutputType.WARNING)
|
|
90
|
+
return ""
|
|
91
|
+
|
|
86
92
|
input_token_count = get_context_token_count(message)
|
|
87
93
|
|
|
88
94
|
if input_token_count > get_max_input_token_count(self.model_group):
|
|
@@ -133,7 +139,9 @@ class BasePlatform(ABC):
|
|
|
133
139
|
first_chunk = None
|
|
134
140
|
|
|
135
141
|
with Status(
|
|
136
|
-
f"🤔 {self.name()} 正在思考中...",
|
|
142
|
+
f"🤔 {(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()} 正在思考中...",
|
|
143
|
+
spinner="dots",
|
|
144
|
+
console=console,
|
|
137
145
|
):
|
|
138
146
|
try:
|
|
139
147
|
while True:
|
|
@@ -147,7 +155,7 @@ class BasePlatform(ABC):
|
|
|
147
155
|
text_content = Text(overflow="fold")
|
|
148
156
|
panel = Panel(
|
|
149
157
|
text_content,
|
|
150
|
-
title=f"[bold cyan]{self.name()}[/bold cyan]",
|
|
158
|
+
title=f"[bold cyan]{(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()}[/bold cyan]",
|
|
151
159
|
subtitle="[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]",
|
|
152
160
|
border_style="bright_blue",
|
|
153
161
|
box=box.ROUNDED,
|
|
@@ -227,7 +235,7 @@ class BasePlatform(ABC):
|
|
|
227
235
|
else:
|
|
228
236
|
# Print a clear prefix line before streaming model output (non-pretty mode)
|
|
229
237
|
console.print(
|
|
230
|
-
f"🤖 模型输出 - {self.name()} (按 Ctrl+C 中断)",
|
|
238
|
+
f"🤖 模型输出 - {(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()} (按 Ctrl+C 中断)",
|
|
231
239
|
soft_wrap=False,
|
|
232
240
|
)
|
|
233
241
|
for s in self.chat(message):
|
|
@@ -369,9 +377,10 @@ class BasePlatform(ABC):
|
|
|
369
377
|
return
|
|
370
378
|
|
|
371
379
|
if self._session_history_file is None:
|
|
372
|
-
# Ensure
|
|
380
|
+
# Ensure session history directory exists under data directory
|
|
373
381
|
data_dir = get_data_dir()
|
|
374
|
-
os.
|
|
382
|
+
session_dir = os.path.join(data_dir, "session_history")
|
|
383
|
+
os.makedirs(session_dir, exist_ok=True)
|
|
375
384
|
|
|
376
385
|
# Build a safe filename including platform, model and timestamp
|
|
377
386
|
try:
|
|
@@ -389,7 +398,7 @@ class BasePlatform(ABC):
|
|
|
389
398
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
390
399
|
|
|
391
400
|
self._session_history_file = os.path.join(
|
|
392
|
-
|
|
401
|
+
session_dir, f"session_history_{safe_platform}_{safe_model}_{ts}.log"
|
|
393
402
|
)
|
|
394
403
|
|
|
395
404
|
# Append record
|
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -633,6 +633,12 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
633
633
|
异常:
|
|
634
634
|
Exception: 如果工具调用缺少必要字段
|
|
635
635
|
"""
|
|
636
|
+
# 如果</TOOL_CALL>出现在响应的末尾,但是前面没有换行符,自动插入一个换行符进行修复
|
|
637
|
+
if content.rstrip().endswith(ct("TOOL_CALL")):
|
|
638
|
+
pos = content.rfind(ct("TOOL_CALL"))
|
|
639
|
+
if pos > 0 and content[pos - 1] not in ("\n", "\r"):
|
|
640
|
+
content = content[:pos] + "\n" + content[pos:]
|
|
641
|
+
|
|
636
642
|
# 将内容拆分为行
|
|
637
643
|
pattern = rf'(?ms){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
|
638
644
|
data = re.findall(pattern, content)
|