jarvis-ai-assistant 0.3.34__py3-none-any.whl → 0.4.1__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 +44 -12
- jarvis/jarvis_agent/agent_manager.py +14 -10
- jarvis/jarvis_agent/config.py +2 -1
- jarvis/jarvis_agent/edit_file_handler.py +2 -2
- jarvis/jarvis_agent/jarvis.py +305 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +143 -0
- jarvis/jarvis_agent/run_loop.py +5 -4
- jarvis/jarvis_agent/stdio_redirect.py +296 -0
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +745 -0
- jarvis/jarvis_code_agent/code_agent.py +10 -12
- jarvis/jarvis_code_analysis/code_review.py +0 -1
- jarvis/jarvis_data/config_schema.json +5 -0
- jarvis/jarvis_multi_agent/__init__.py +205 -25
- jarvis/jarvis_multi_agent/main.py +10 -2
- jarvis/jarvis_platform/base.py +16 -6
- jarvis/jarvis_tools/sub_agent.py +11 -38
- jarvis/jarvis_tools/sub_code_agent.py +3 -1
- jarvis/jarvis_utils/config.py +12 -2
- {jarvis_ai_assistant-0.3.34.dist-info → jarvis_ai_assistant-0.4.1.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.34.dist-info → jarvis_ai_assistant-0.4.1.dist-info}/RECORD +28 -25
- jarvis/jarvis_tools/edit_file.py +0 -208
- jarvis/jarvis_tools/rewrite_file.py +0 -191
- {jarvis_ai_assistant-0.3.34.dist-info → jarvis_ai_assistant-0.4.1.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.34.dist-info → jarvis_ai_assistant-0.4.1.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.34.dist-info → jarvis_ai_assistant-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.34.dist-info → jarvis_ai_assistant-0.4.1.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,16 +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
|
-
input_handler=[
|
95
|
-
shell_input_handler,
|
96
|
-
file_context_handler,
|
97
|
-
builtin_input_handler,
|
98
|
-
],
|
99
94
|
need_summary=need_summary,
|
100
95
|
use_methodology=False, # 禁用方法论
|
101
96
|
use_analysis=False, # 禁用分析
|
97
|
+
non_interactive=self.non_interactive,
|
102
98
|
)
|
103
99
|
|
104
100
|
self.agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
@@ -142,7 +138,7 @@ class CodeAgent:
|
|
142
138
|
|
143
139
|
## 文件编辑工具使用规范
|
144
140
|
- 对于部分文件内容修改,使用edit_file工具
|
145
|
-
- 对于需要重写整个文件内容,使用
|
141
|
+
- 对于需要重写整个文件内容,使用 REWRITE 操作
|
146
142
|
- 对于简单的修改,可以使用execute_script工具执行shell命令完成
|
147
143
|
|
148
144
|
## 子任务与子CodeAgent
|
@@ -572,8 +568,8 @@ class CodeAgent:
|
|
572
568
|
1. 每次响应仅执行一步操作,先分析再修改,避免一步多改。
|
573
569
|
2. 充分利用工具理解用户需求和现有代码,禁止凭空假设。
|
574
570
|
3. 如果不清楚要修改的文件,必须先分析并找出需要修改的文件,明确目标后再进行编辑。
|
575
|
-
4. 代码编辑任务优先使用
|
576
|
-
5. 如需大范围重写,才可使用
|
571
|
+
4. 代码编辑任务优先使用 PATCH 操作,确保搜索文本在目标文件中有且仅有一次精确匹配,保证修改的准确性和安全性。
|
572
|
+
5. 如需大范围重写,才可使用 REWRITE 操作。
|
577
573
|
6. 如遇信息不明,优先调用工具补充分析,不要主观臆断。
|
578
574
|
"""
|
579
575
|
|
@@ -856,9 +852,10 @@ def cli(
|
|
856
852
|
PrettyOutput.print(
|
857
853
|
f"警告:当前目录 '{curr_dir_path}' 不是一个git仓库。", OutputType.WARNING
|
858
854
|
)
|
859
|
-
if user_confirm(
|
855
|
+
init_git = True if non_interactive else user_confirm(
|
860
856
|
f"是否要在 '{curr_dir_path}' 中初始化一个新的git仓库?", default=True
|
861
|
-
)
|
857
|
+
)
|
858
|
+
if init_git:
|
862
859
|
try:
|
863
860
|
subprocess.run(
|
864
861
|
["git", "init"],
|
@@ -886,6 +883,7 @@ def cli(
|
|
886
883
|
need_summary=False,
|
887
884
|
append_tools=append_tools,
|
888
885
|
tool_group=tool_group,
|
886
|
+
non_interactive=non_interactive,
|
889
887
|
)
|
890
888
|
|
891
889
|
# 尝试恢复会话
|
@@ -322,6 +322,11 @@
|
|
322
322
|
"description": "是否保存会话记录",
|
323
323
|
"default": false
|
324
324
|
},
|
325
|
+
"JARVIS_SKIP_PREDEFINED_TASKS": {
|
326
|
+
"type": "boolean",
|
327
|
+
"description": "是否跳过预定义任务加载(不读取 pre-command 列表)",
|
328
|
+
"default": false
|
329
|
+
},
|
325
330
|
"JARVIS_GIT_CHECK_MODE": {
|
326
331
|
"type": "string",
|
327
332
|
"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:
|
74
142
|
return (
|
75
143
|
False,
|
76
|
-
"
|
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:
|
167
|
+
return (
|
168
|
+
False,
|
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
|
@@ -113,24 +288,22 @@ content: |2
|
|
113
288
|
|
114
289
|
config = self.agents_config_map[name].copy()
|
115
290
|
|
291
|
+
# Prepend common system prompt if configured
|
292
|
+
common_sp = getattr(self, "common_system_prompt", "")
|
293
|
+
if common_sp:
|
294
|
+
existing_sp = config.get("system_prompt", "")
|
295
|
+
if existing_sp:
|
296
|
+
config["system_prompt"] = f"{common_sp}\n\n{existing_sp}"
|
297
|
+
else:
|
298
|
+
config["system_prompt"] = common_sp
|
299
|
+
|
116
300
|
if name != self.main_agent_name and self.original_question:
|
117
301
|
system_prompt = config.get("system_prompt", "")
|
118
302
|
config["system_prompt"] = (
|
119
303
|
f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
|
120
304
|
)
|
121
305
|
|
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)
|
306
|
+
agent = Agent(output_handler=[ToolRegistry(), EditFileHandler(), RewriteFileHandler(), self],**config)
|
134
307
|
self.agents[name] = agent
|
135
308
|
return agent
|
136
309
|
|
@@ -204,6 +377,13 @@ content: {msg['content']}
|
|
204
377
|
if not agent:
|
205
378
|
return f"智能体 {to_agent_name} 未找到"
|
206
379
|
|
380
|
+
# Check if the sending agent should be cleared
|
381
|
+
sender_config = self.agents_config_map.get(last_agent_name, {})
|
382
|
+
if sender_config.get("clear_after_send_message"):
|
383
|
+
if agent:
|
384
|
+
PrettyOutput.print(f"清除智能体 {last_agent_name} 在发送消息后的历史记录...", OutputType.INFO)
|
385
|
+
agent.clear_history()
|
386
|
+
|
207
387
|
last_agent_name = agent.name
|
208
388
|
msg = agent.run(prompt)
|
209
389
|
return ""
|
@@ -20,10 +20,12 @@ 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
|
),
|
26
|
-
),
|
27
29
|
):
|
28
30
|
"""从YAML配置文件初始化并运行多智能体系统"""
|
29
31
|
# CLI 标志:非交互模式(不依赖配置文件)
|
@@ -46,6 +48,8 @@ def cli(
|
|
46
48
|
try:
|
47
49
|
if non_interactive:
|
48
50
|
set_config("JARVIS_NON_INTERACTIVE", True)
|
51
|
+
if model_group:
|
52
|
+
set_config("JARVIS_LLM_GROUP", str(model_group))
|
49
53
|
except Exception:
|
50
54
|
# 静默忽略同步异常,不影响主流程
|
51
55
|
pass
|
@@ -62,7 +66,11 @@ def cli(
|
|
62
66
|
raise ValueError("必须指定main_agent作为主智能体")
|
63
67
|
|
64
68
|
# 创建并运行多智能体系统
|
65
|
-
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
|
+
)
|
66
74
|
final_input = (
|
67
75
|
user_input
|
68
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,
|
@@ -223,10 +231,11 @@ class BasePlatform(ABC):
|
|
223
231
|
duration = end_time - start_time
|
224
232
|
panel.subtitle = f"[bold green]✓ 对话完成耗时: {duration:.2f}秒[/bold green]"
|
225
233
|
live.update(panel)
|
234
|
+
console.print()
|
226
235
|
else:
|
227
236
|
# Print a clear prefix line before streaming model output (non-pretty mode)
|
228
237
|
console.print(
|
229
|
-
f"🤖 模型输出 - {self.name()} (按 Ctrl+C 中断)",
|
238
|
+
f"🤖 模型输出 - {(G.current_agent_name + ' · ') if G.current_agent_name else ''}{self.name()} (按 Ctrl+C 中断)",
|
230
239
|
soft_wrap=False,
|
231
240
|
)
|
232
241
|
for s in self.chat(message):
|
@@ -368,9 +377,10 @@ class BasePlatform(ABC):
|
|
368
377
|
return
|
369
378
|
|
370
379
|
if self._session_history_file is None:
|
371
|
-
# Ensure
|
380
|
+
# Ensure session history directory exists under data directory
|
372
381
|
data_dir = get_data_dir()
|
373
|
-
os.
|
382
|
+
session_dir = os.path.join(data_dir, "session_history")
|
383
|
+
os.makedirs(session_dir, exist_ok=True)
|
374
384
|
|
375
385
|
# Build a safe filename including platform, model and timestamp
|
376
386
|
try:
|
@@ -388,7 +398,7 @@ class BasePlatform(ABC):
|
|
388
398
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
389
399
|
|
390
400
|
self._session_history_file = os.path.join(
|
391
|
-
|
401
|
+
session_dir, f"session_history_{safe_platform}_{safe_model}_{ts}.log"
|
392
402
|
)
|
393
403
|
|
394
404
|
# Append record
|
jarvis/jarvis_tools/sub_agent.py
CHANGED
@@ -5,7 +5,7 @@ sub_agent 工具
|
|
5
5
|
|
6
6
|
约定:
|
7
7
|
- 必填参数:task, name, background, system_prompt, summary_prompt, use_tools
|
8
|
-
- 继承父 Agent 的部分配置:model_group、input_handler、execute_tool_confirm、multiline_inputer;其他参数需显式提供
|
8
|
+
- 继承父 Agent 的部分配置:model_group、input_handler、execute_tool_confirm、multiline_inputer、non_interactive、use_methodology、use_analysis;其他参数需显式提供
|
9
9
|
- 子Agent必须自动完成(auto_complete=True)且需要summary(need_summary=True)
|
10
10
|
"""
|
11
11
|
from typing import Any, Dict, List
|
@@ -27,7 +27,7 @@ class SubAgentTool:
|
|
27
27
|
|
28
28
|
# 必须与文件名一致,供 ToolRegistry 自动注册
|
29
29
|
name = "sub_agent"
|
30
|
-
description = "将子任务交给通用 Agent 执行,并返回执行结果(继承父Agent部分配置:model_group、input_handler、execute_tool_confirm、multiline_inputer;其他参数需显式提供,自动完成并生成总结)。"
|
30
|
+
description = "将子任务交给通用 Agent 执行,并返回执行结果(继承父Agent部分配置:model_group、input_handler、execute_tool_confirm、multiline_inputer、non_interactive、use_methodology、use_analysis;其他参数需显式提供,自动完成并生成总结)。"
|
31
31
|
parameters = {
|
32
32
|
"type": "object",
|
33
33
|
"properties": {
|
@@ -51,17 +51,6 @@ class SubAgentTool:
|
|
51
51
|
"type": "string",
|
52
52
|
"description": "覆盖子Agent的总结提示词(必填)",
|
53
53
|
},
|
54
|
-
"use_tools": {
|
55
|
-
"type": "array",
|
56
|
-
"items": {"type": "string"},
|
57
|
-
"description": "限制子Agent可用的工具名称列表(必填)。兼容以逗号分隔的字符串输入。可用的工具列表:"
|
58
|
-
+ "\n".join(
|
59
|
-
[
|
60
|
-
t["name"] + ": " + t["description"]
|
61
|
-
for t in ToolRegistry().get_all_tools()
|
62
|
-
]
|
63
|
-
),
|
64
|
-
},
|
65
54
|
},
|
66
55
|
"required": [
|
67
56
|
"task",
|
@@ -69,7 +58,6 @@ class SubAgentTool:
|
|
69
58
|
"background",
|
70
59
|
"system_prompt",
|
71
60
|
"summary_prompt",
|
72
|
-
"use_tools",
|
73
61
|
],
|
74
62
|
}
|
75
63
|
|
@@ -105,16 +93,6 @@ class SubAgentTool:
|
|
105
93
|
summary_prompt = str(args.get("summary_prompt", "")).strip()
|
106
94
|
agent_name = str(args.get("name", "")).strip()
|
107
95
|
|
108
|
-
# 解析可用工具列表(支持数组或以逗号分隔的字符串)
|
109
|
-
_use_tools = args.get("use_tools", None)
|
110
|
-
use_tools: List[str] = []
|
111
|
-
if isinstance(_use_tools, list):
|
112
|
-
use_tools = [str(x).strip() for x in _use_tools if str(x).strip()]
|
113
|
-
elif isinstance(_use_tools, str):
|
114
|
-
use_tools = [s.strip() for s in _use_tools.split(",") if s.strip()]
|
115
|
-
else:
|
116
|
-
use_tools = []
|
117
|
-
|
118
96
|
errors = []
|
119
97
|
if not system_prompt:
|
120
98
|
errors.append("system_prompt 不能为空")
|
@@ -122,8 +100,6 @@ class SubAgentTool:
|
|
122
100
|
errors.append("summary_prompt 不能为空")
|
123
101
|
if not agent_name:
|
124
102
|
errors.append("name 不能为空")
|
125
|
-
if not use_tools:
|
126
|
-
errors.append("use_tools 不能为空")
|
127
103
|
if not background:
|
128
104
|
errors.append("background 不能为空")
|
129
105
|
|
@@ -137,16 +113,20 @@ class SubAgentTool:
|
|
137
113
|
# 基于父Agent(如有)继承部分配置后创建子Agent
|
138
114
|
parent_agent = args.get("agent", None)
|
139
115
|
parent_model_group = None
|
140
|
-
parent_input_handler = None
|
141
116
|
parent_execute_tool_confirm = None
|
142
117
|
parent_multiline_inputer = None
|
118
|
+
parent_non_interactive = None
|
119
|
+
parent_use_methodology = None
|
120
|
+
parent_use_analysis = None
|
143
121
|
try:
|
144
122
|
if parent_agent is not None:
|
145
123
|
if getattr(parent_agent, "model", None):
|
146
124
|
parent_model_group = getattr(parent_agent.model, "model_group", None)
|
147
|
-
parent_input_handler = getattr(parent_agent, "input_handler", None)
|
148
125
|
parent_execute_tool_confirm = getattr(parent_agent, "execute_tool_confirm", None)
|
149
126
|
parent_multiline_inputer = getattr(parent_agent, "multiline_inputer", None)
|
127
|
+
parent_non_interactive = getattr(parent_agent, "non_interactive", None)
|
128
|
+
parent_use_methodology = getattr(parent_agent, "use_methodology", None)
|
129
|
+
parent_use_analysis = getattr(parent_agent, "use_analysis", None)
|
150
130
|
except Exception:
|
151
131
|
# 安全兜底:无法从父Agent获取配置则保持为None,使用系统默认
|
152
132
|
pass
|
@@ -158,24 +138,17 @@ class SubAgentTool:
|
|
158
138
|
model_group=parent_model_group,
|
159
139
|
summary_prompt=summary_prompt,
|
160
140
|
auto_complete=auto_complete,
|
161
|
-
output_handler=None,
|
162
141
|
use_tools=None,
|
163
|
-
input_handler=parent_input_handler,
|
164
142
|
execute_tool_confirm=parent_execute_tool_confirm,
|
165
143
|
need_summary=need_summary,
|
166
144
|
multiline_inputer=parent_multiline_inputer,
|
167
|
-
use_methodology=
|
168
|
-
use_analysis=
|
145
|
+
use_methodology=parent_use_methodology,
|
146
|
+
use_analysis=parent_use_analysis,
|
169
147
|
force_save_memory=None,
|
170
148
|
files=None,
|
149
|
+
non_interactive=parent_non_interactive,
|
171
150
|
)
|
172
151
|
|
173
|
-
# 设置可用工具列表
|
174
|
-
try:
|
175
|
-
agent.set_use_tools(use_tools)
|
176
|
-
except Exception:
|
177
|
-
pass
|
178
|
-
|
179
152
|
# 校验子Agent所用模型是否有效,必要时回退到平台可用模型
|
180
153
|
try:
|
181
154
|
platform = getattr(agent, "model", None)
|
@@ -84,6 +84,7 @@ class SubCodeAgentTool:
|
|
84
84
|
parent_agent = None
|
85
85
|
except Exception:
|
86
86
|
parent_agent = None
|
87
|
+
parent_non_interactive = getattr(parent_agent, "non_interactive", None) if parent_agent is not None else None
|
87
88
|
model_group = None
|
88
89
|
use_tools: List[str] = []
|
89
90
|
try:
|
@@ -115,7 +116,7 @@ class SubCodeAgentTool:
|
|
115
116
|
"search_web",
|
116
117
|
"ask_user",
|
117
118
|
"read_code",
|
118
|
-
|
119
|
+
|
119
120
|
"save_memory",
|
120
121
|
"retrieve_memory",
|
121
122
|
"clear_memory",
|
@@ -138,6 +139,7 @@ class SubCodeAgentTool:
|
|
138
139
|
need_summary=True,
|
139
140
|
append_tools=append_tools,
|
140
141
|
tool_group=tool_group,
|
142
|
+
non_interactive=parent_non_interactive,
|
141
143
|
)
|
142
144
|
except SystemExit as se:
|
143
145
|
# 将底层 sys.exit 转换为工具错误,避免终止进程
|
jarvis/jarvis_utils/config.py
CHANGED
@@ -81,10 +81,10 @@ def get_max_token_count(model_group_override: Optional[str] = None) -> int:
|
|
81
81
|
获取模型允许的最大token数量。
|
82
82
|
|
83
83
|
返回:
|
84
|
-
int: 模型能处理的最大token数量,为最大输入token数量的
|
84
|
+
int: 模型能处理的最大token数量,为最大输入token数量的30倍。
|
85
85
|
"""
|
86
86
|
max_input_tokens = get_max_input_token_count(model_group_override)
|
87
|
-
return max_input_tokens *
|
87
|
+
return max_input_tokens * 30
|
88
88
|
|
89
89
|
|
90
90
|
def get_max_input_token_count(model_group_override: Optional[str] = None) -> int:
|
@@ -779,3 +779,13 @@ def is_non_interactive() -> bool:
|
|
779
779
|
# 忽略环境变量解析异常,回退到配置
|
780
780
|
pass
|
781
781
|
return GLOBAL_CONFIG_DATA.get("JARVIS_NON_INTERACTIVE", False) is True
|
782
|
+
|
783
|
+
|
784
|
+
def is_skip_predefined_tasks() -> bool:
|
785
|
+
"""
|
786
|
+
是否跳过预定义任务加载。
|
787
|
+
|
788
|
+
返回:
|
789
|
+
bool: 如果跳过预定义任务加载则返回True,默认为False
|
790
|
+
"""
|
791
|
+
return GLOBAL_CONFIG_DATA.get("JARVIS_SKIP_PREDEFINED_TASKS", False) is True
|