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
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
- 列出所有方法论
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import argparse
|
|
12
11
|
import hashlib
|
|
13
12
|
import json
|
|
14
13
|
import os
|
|
15
14
|
|
|
15
|
+
import typer
|
|
16
16
|
import yaml # type: ignore
|
|
17
17
|
|
|
18
18
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
@@ -22,8 +22,13 @@ from jarvis.jarvis_utils.methodology import (
|
|
|
22
22
|
)
|
|
23
23
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
24
24
|
|
|
25
|
+
app = typer.Typer(help="方法论管理工具")
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
@app.command("import")
|
|
29
|
+
def import_methodology(
|
|
30
|
+
input_file: str = typer.Argument(..., help="要导入的方法论文件路径")
|
|
31
|
+
):
|
|
27
32
|
"""导入方法论文件(合并策略)"""
|
|
28
33
|
try:
|
|
29
34
|
# 加载现有方法论
|
|
@@ -56,9 +61,11 @@ def import_methodology(input_file):
|
|
|
56
61
|
)
|
|
57
62
|
except (json.JSONDecodeError, OSError) as e:
|
|
58
63
|
PrettyOutput.print(f"导入失败: {str(e)}", OutputType.ERROR)
|
|
64
|
+
raise typer.Exit(code=1)
|
|
59
65
|
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
@app.command("export")
|
|
68
|
+
def export_methodology(output_file: str = typer.Argument(..., help="导出文件路径")):
|
|
62
69
|
"""导出当前方法论到单个文件"""
|
|
63
70
|
try:
|
|
64
71
|
methodologies = _load_all_methodologies()
|
|
@@ -72,8 +79,10 @@ def export_methodology(output_file):
|
|
|
72
79
|
)
|
|
73
80
|
except (OSError, TypeError) as e:
|
|
74
81
|
PrettyOutput.print(f"导出失败: {str(e)}", OutputType.ERROR)
|
|
82
|
+
raise typer.Exit(code=1)
|
|
75
83
|
|
|
76
84
|
|
|
85
|
+
@app.command("list")
|
|
77
86
|
def list_methodologies():
|
|
78
87
|
"""列出所有方法论"""
|
|
79
88
|
try:
|
|
@@ -83,14 +92,20 @@ def list_methodologies():
|
|
|
83
92
|
PrettyOutput.print("没有找到方法论", OutputType.INFO)
|
|
84
93
|
return
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
# 先拼接再统一打印,避免在循环中逐条输出造成信息稀疏
|
|
96
|
+
lines = ["可用方法论:"]
|
|
87
97
|
for i, (problem_type, _) in enumerate(methodologies.items(), 1):
|
|
88
|
-
|
|
98
|
+
lines.append(f"{i}. {problem_type}")
|
|
99
|
+
PrettyOutput.print("\n".join(lines), OutputType.INFO)
|
|
89
100
|
except (OSError, json.JSONDecodeError) as e:
|
|
90
101
|
PrettyOutput.print(f"列出方法论失败: {str(e)}", OutputType.ERROR)
|
|
102
|
+
raise typer.Exit(code=1)
|
|
91
103
|
|
|
92
104
|
|
|
93
|
-
|
|
105
|
+
@app.command("extract")
|
|
106
|
+
def extract_methodology(
|
|
107
|
+
input_file: str = typer.Argument(..., help="要提取方法论的文本文件路径")
|
|
108
|
+
):
|
|
94
109
|
"""从文本文件中提取方法论"""
|
|
95
110
|
try:
|
|
96
111
|
# 读取文本文件内容
|
|
@@ -129,23 +144,23 @@ def extract_methodology(input_file):
|
|
|
129
144
|
"""
|
|
130
145
|
|
|
131
146
|
# 调用大模型平台提取方法论
|
|
132
|
-
print("
|
|
147
|
+
PrettyOutput.print("正在提取方法论...", OutputType.INFO)
|
|
133
148
|
try:
|
|
134
149
|
response = platform.chat_until_success(prompt)
|
|
135
150
|
except Exception as e:
|
|
136
|
-
print("
|
|
151
|
+
PrettyOutput.print("提取失败", OutputType.ERROR)
|
|
137
152
|
PrettyOutput.print(f"提取方法论失败: {str(e)}", OutputType.ERROR)
|
|
138
|
-
|
|
153
|
+
raise typer.Exit(code=1)
|
|
139
154
|
|
|
140
155
|
# 提取YAML部分
|
|
141
156
|
methodologies_start = response.find("<methodologies>") + len("<methodologies>")
|
|
142
157
|
methodologies_end = response.find("</methodologies>")
|
|
143
158
|
if methodologies_start == -1 or methodologies_end == -1:
|
|
144
|
-
print("
|
|
159
|
+
PrettyOutput.print("响应格式无效", OutputType.ERROR)
|
|
145
160
|
PrettyOutput.print(
|
|
146
161
|
"大模型未返回有效的<methodologies>格式", OutputType.ERROR
|
|
147
162
|
)
|
|
148
|
-
|
|
163
|
+
raise typer.Exit(code=1)
|
|
149
164
|
|
|
150
165
|
yaml_content = response[methodologies_start:methodologies_end].strip()
|
|
151
166
|
|
|
@@ -155,14 +170,14 @@ def extract_methodology(input_file):
|
|
|
155
170
|
item["problem_type"]: item["content"] for item in data
|
|
156
171
|
}
|
|
157
172
|
except (yaml.YAMLError, KeyError, TypeError) as e:
|
|
158
|
-
print("
|
|
173
|
+
PrettyOutput.print("YAML解析失败", OutputType.ERROR)
|
|
159
174
|
PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
|
|
160
|
-
|
|
175
|
+
raise typer.Exit(code=1)
|
|
161
176
|
|
|
162
177
|
if not extracted_methodologies:
|
|
163
|
-
print("
|
|
178
|
+
PrettyOutput.print("未提取到有效方法论", OutputType.WARNING)
|
|
164
179
|
return
|
|
165
|
-
print("
|
|
180
|
+
PrettyOutput.print("提取到有效方法论", OutputType.SUCCESS)
|
|
166
181
|
|
|
167
182
|
# 加载现有方法论
|
|
168
183
|
existing_methodologies = _load_all_methodologies()
|
|
@@ -190,9 +205,13 @@ def extract_methodology(input_file):
|
|
|
190
205
|
)
|
|
191
206
|
except Exception as e:
|
|
192
207
|
PrettyOutput.print(f"提取失败: {str(e)}", OutputType.ERROR)
|
|
208
|
+
raise typer.Exit(code=1)
|
|
193
209
|
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
@app.command("extract-url")
|
|
212
|
+
def extract_methodology_from_url(
|
|
213
|
+
url: str = typer.Argument(..., help="要提取方法论的URL")
|
|
214
|
+
):
|
|
196
215
|
"""从URL提取方法论"""
|
|
197
216
|
try:
|
|
198
217
|
# 获取平台实例
|
|
@@ -228,23 +247,23 @@ def extract_methodology_from_url(url):
|
|
|
228
247
|
6. 内容字段使用|保留多行格式
|
|
229
248
|
"""
|
|
230
249
|
# 调用大模型平台提取方法论
|
|
231
|
-
print("
|
|
250
|
+
PrettyOutput.print("正在从URL提取方法论...", OutputType.INFO)
|
|
232
251
|
try:
|
|
233
252
|
response = platform.chat_until_success(prompt)
|
|
234
253
|
except Exception as e:
|
|
235
|
-
print("
|
|
254
|
+
PrettyOutput.print("提取失败", OutputType.ERROR)
|
|
236
255
|
PrettyOutput.print(f"提取方法论失败: {str(e)}", OutputType.ERROR)
|
|
237
|
-
|
|
256
|
+
raise typer.Exit(code=1)
|
|
238
257
|
|
|
239
258
|
# 提取YAML部分
|
|
240
259
|
methodologies_start = response.find("<methodologies>") + len("<methodologies>")
|
|
241
260
|
methodologies_end = response.find("</methodologies>")
|
|
242
261
|
if methodologies_start == -1 or methodologies_end == -1:
|
|
243
|
-
print("
|
|
262
|
+
PrettyOutput.print("响应格式无效", OutputType.ERROR)
|
|
244
263
|
PrettyOutput.print(
|
|
245
264
|
"大模型未返回有效的<methodologies>格式", OutputType.ERROR
|
|
246
265
|
)
|
|
247
|
-
|
|
266
|
+
raise typer.Exit(code=1)
|
|
248
267
|
|
|
249
268
|
yaml_content = response[methodologies_start:methodologies_end].strip()
|
|
250
269
|
|
|
@@ -254,14 +273,14 @@ def extract_methodology_from_url(url):
|
|
|
254
273
|
item["problem_type"]: item["content"] for item in data
|
|
255
274
|
}
|
|
256
275
|
except (yaml.YAMLError, KeyError, TypeError) as e:
|
|
257
|
-
print("
|
|
276
|
+
PrettyOutput.print("YAML解析失败", OutputType.ERROR)
|
|
258
277
|
PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
|
|
259
|
-
|
|
278
|
+
raise typer.Exit(code=1)
|
|
260
279
|
|
|
261
280
|
if not extracted_methodologies:
|
|
262
|
-
print("
|
|
281
|
+
PrettyOutput.print("未提取到有效方法论", OutputType.WARNING)
|
|
263
282
|
return
|
|
264
|
-
print("
|
|
283
|
+
PrettyOutput.print("提取到有效方法论", OutputType.SUCCESS)
|
|
265
284
|
|
|
266
285
|
# 加载现有方法论
|
|
267
286
|
existing_methodologies = _load_all_methodologies()
|
|
@@ -289,46 +308,12 @@ def extract_methodology_from_url(url):
|
|
|
289
308
|
)
|
|
290
309
|
except Exception as e:
|
|
291
310
|
PrettyOutput.print(f"从URL提取失败: {str(e)}", OutputType.ERROR)
|
|
311
|
+
raise typer.Exit(code=1)
|
|
292
312
|
|
|
293
313
|
|
|
294
|
-
def main():
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
|
-
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
298
|
-
|
|
299
|
-
# import命令
|
|
300
|
-
import_parser = subparsers.add_parser("import", help="导入方法论文件(合并策略)")
|
|
301
|
-
import_parser.add_argument("input_file", type=str, help="要导入的方法论文件路径")
|
|
302
|
-
|
|
303
|
-
# export命令
|
|
304
|
-
export_parser = subparsers.add_parser("export", help="导出当前方法论到单个文件")
|
|
305
|
-
export_parser.add_argument("output_file", type=str, help="导出文件路径")
|
|
306
|
-
|
|
307
|
-
# list命令
|
|
308
|
-
subparsers.add_parser("list", help="列出所有方法论")
|
|
309
|
-
|
|
310
|
-
# extract命令
|
|
311
|
-
extract_parser = subparsers.add_parser("extract", help="从文本文件中提取方法论")
|
|
312
|
-
extract_parser.add_argument(
|
|
313
|
-
"input_file", type=str, help="要提取方法论的文本文件路径"
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
# extract-url命令
|
|
317
|
-
extract_url_parser = subparsers.add_parser("extract-url", help="从URL提取方法论")
|
|
318
|
-
extract_url_parser.add_argument("url", type=str, help="要提取方法论的URL")
|
|
319
|
-
|
|
320
|
-
args = parser.parse_args()
|
|
321
|
-
|
|
322
|
-
if args.command == "import":
|
|
323
|
-
import_methodology(args.input_file)
|
|
324
|
-
elif args.command == "export":
|
|
325
|
-
export_methodology(args.output_file)
|
|
326
|
-
elif args.command == "list":
|
|
327
|
-
list_methodologies()
|
|
328
|
-
elif args.command == "extract":
|
|
329
|
-
extract_methodology(args.input_file)
|
|
330
|
-
elif args.command == "extract-url":
|
|
331
|
-
extract_methodology_from_url(args.url)
|
|
314
|
+
def main() -> None:
|
|
315
|
+
"""Application entry point"""
|
|
316
|
+
app()
|
|
332
317
|
|
|
333
318
|
|
|
334
319
|
if __name__ == "__main__":
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import re
|
|
3
|
-
from typing import Any, Dict, List, Tuple
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
4
4
|
|
|
5
5
|
import yaml
|
|
6
6
|
|
|
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
|
-
self.
|
|
18
|
-
self.
|
|
19
|
+
self.agents_config_map = {c["name"]: c for c in agents_config}
|
|
20
|
+
self.agents: Dict[str, Agent] = {}
|
|
19
21
|
self.main_agent_name = main_agent_name
|
|
22
|
+
self.original_question: Optional[str] = None
|
|
23
|
+
self.common_system_prompt: str = common_system_prompt
|
|
20
24
|
|
|
21
25
|
def prompt(self) -> str:
|
|
22
26
|
return f"""
|
|
@@ -65,18 +69,168 @@ content: |2
|
|
|
65
69
|
"""
|
|
66
70
|
|
|
67
71
|
def can_handle(self, response: str) -> bool:
|
|
68
|
-
|
|
72
|
+
# 只要检测到 SEND_MESSAGE 起始标签即认为可处理,
|
|
73
|
+
# 即便内容有误也由 handle 返回明确错误与修复指导
|
|
74
|
+
return ot("SEND_MESSAGE") in response
|
|
69
75
|
|
|
70
76
|
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
|
|
71
|
-
|
|
72
|
-
|
|
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:
|
|
73
167
|
return (
|
|
74
168
|
False,
|
|
75
|
-
|
|
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')}"
|
|
76
233
|
)
|
|
77
|
-
if len(send_messages) == 0:
|
|
78
|
-
return False, ""
|
|
79
|
-
return True, send_messages[0]
|
|
80
234
|
|
|
81
235
|
def name(self) -> str:
|
|
82
236
|
return "SEND_MESSAGE"
|
|
@@ -88,56 +242,161 @@ content: |2
|
|
|
88
242
|
Args:
|
|
89
243
|
content: The content containing send message
|
|
90
244
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
93
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
|
+
|
|
94
272
|
ret = []
|
|
95
273
|
for item in data:
|
|
96
274
|
try:
|
|
97
275
|
msg = yaml.safe_load(item)
|
|
98
|
-
if "to" in msg and "content" in msg:
|
|
276
|
+
if isinstance(msg, dict) and "to" in msg and "content" in msg:
|
|
99
277
|
ret.append(msg)
|
|
100
|
-
except Exception
|
|
278
|
+
except Exception:
|
|
101
279
|
continue
|
|
102
280
|
return ret
|
|
103
281
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
282
|
+
def _get_agent(self, name: str) -> Union[Agent, None]:
|
|
283
|
+
if name in self.agents:
|
|
284
|
+
return self.agents[name]
|
|
285
|
+
|
|
286
|
+
if name not in self.agents_config_map:
|
|
287
|
+
return None
|
|
288
|
+
|
|
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}"
|
|
112
302
|
else:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
303
|
+
config["system_prompt"] = common_sp
|
|
304
|
+
|
|
305
|
+
if name != self.main_agent_name and self.original_question:
|
|
306
|
+
system_prompt = config.get("system_prompt", "")
|
|
307
|
+
config["system_prompt"] = (
|
|
308
|
+
f"{system_prompt}\n\n# 原始问题\n{self.original_question}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
agent = Agent(output_handler=[ToolRegistry(), EditFileHandler(), RewriteFileHandler(), self],**config)
|
|
312
|
+
self.agents[name] = agent
|
|
313
|
+
return agent
|
|
117
314
|
|
|
118
315
|
def run(self, user_input: str) -> str:
|
|
119
|
-
|
|
120
|
-
|
|
316
|
+
self.original_question = user_input
|
|
317
|
+
last_agent_name = self.main_agent_name
|
|
318
|
+
|
|
319
|
+
agent = self._get_agent(self.main_agent_name)
|
|
320
|
+
if not agent:
|
|
321
|
+
# This should not happen if main_agent_name is correctly configured
|
|
322
|
+
return f"主智能体 {self.main_agent_name} 未找到"
|
|
323
|
+
|
|
324
|
+
msg: Any = agent.run(user_input)
|
|
325
|
+
|
|
121
326
|
while msg:
|
|
122
327
|
if isinstance(msg, str):
|
|
123
328
|
return msg
|
|
124
|
-
|
|
125
|
-
|
|
329
|
+
|
|
330
|
+
if not isinstance(msg, Dict):
|
|
331
|
+
# Should not happen if agent.run() returns str or Dict
|
|
332
|
+
PrettyOutput.print(f"未知消息类型: {type(msg)}", OutputType.WARNING)
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
# Generate a brief summary via direct model call to avoid run-loop recursion
|
|
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 = """
|
|
344
|
+
请基于当前会话,为即将发送给其他智能体的协作交接写一段摘要,包含:
|
|
345
|
+
- 已完成的主要工作与产出
|
|
346
|
+
- 关键决策及其理由
|
|
347
|
+
- 已知的约束/风险/边界条件
|
|
348
|
+
- 未解决的问题与待澄清点
|
|
349
|
+
- 下一步建议与对目标智能体的具体请求
|
|
350
|
+
要求:
|
|
351
|
+
- 仅输出纯文本,不包含任何指令或工具调用
|
|
352
|
+
- 使用简洁的要点式表述
|
|
353
|
+
""".strip()
|
|
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 = ""
|
|
360
|
+
prompt = f"""
|
|
126
361
|
Please handle this message:
|
|
127
|
-
from: {
|
|
362
|
+
from: {last_agent_name}
|
|
363
|
+
summary_of_sender_work: {summary_text}
|
|
128
364
|
content: {msg['content']}
|
|
129
365
|
"""
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
f"未找到智能体 {msg['to']},可用智能体列表: {self.agents.keys()}"
|
|
136
|
-
)
|
|
137
|
-
continue
|
|
366
|
+
to_agent_name = msg.get("to")
|
|
367
|
+
if not to_agent_name:
|
|
368
|
+
return "消息中未指定 `to` 字段"
|
|
369
|
+
|
|
370
|
+
if to_agent_name not in self.agents_config_map:
|
|
138
371
|
PrettyOutput.print(
|
|
139
|
-
f"
|
|
372
|
+
f"未找到智能体 {to_agent_name},正在重试...", OutputType.WARNING
|
|
373
|
+
)
|
|
374
|
+
agent = self._get_agent(last_agent_name)
|
|
375
|
+
if not agent:
|
|
376
|
+
return f"智能体 {last_agent_name} 未找到"
|
|
377
|
+
msg = agent.run(
|
|
378
|
+
f"未找到智能体 {to_agent_name},可用智能体列表: {list(self.agents_config_map.keys())}"
|
|
140
379
|
)
|
|
141
|
-
|
|
142
|
-
|
|
380
|
+
continue
|
|
381
|
+
|
|
382
|
+
PrettyOutput.print(
|
|
383
|
+
f"{last_agent_name} 正在向 {to_agent_name} 发送消息...", OutputType.INFO
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Keep a reference to the sender before switching to the receiver
|
|
387
|
+
sender_agent = agent
|
|
388
|
+
|
|
389
|
+
agent = self._get_agent(to_agent_name)
|
|
390
|
+
if not agent:
|
|
391
|
+
return f"智能体 {to_agent_name} 未找到"
|
|
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
|
+
|
|
400
|
+
last_agent_name = agent.name
|
|
401
|
+
msg = agent.run(prompt)
|
|
143
402
|
return ""
|