jarvis-ai-assistant 0.3.23__py3-none-any.whl → 0.3.24__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 +96 -13
- jarvis/jarvis_agent/agent_manager.py +0 -3
- jarvis/jarvis_agent/jarvis.py +2 -17
- jarvis/jarvis_agent/main.py +2 -8
- jarvis/jarvis_code_agent/code_agent.py +5 -11
- jarvis/jarvis_code_analysis/code_review.py +12 -40
- jarvis/jarvis_data/config_schema.json +4 -18
- jarvis/jarvis_git_utils/git_commiter.py +7 -22
- jarvis/jarvis_mcp/sse_mcp_client.py +4 -3
- jarvis/jarvis_mcp/streamable_mcp_client.py +9 -8
- jarvis/jarvis_memory_organizer/memory_organizer.py +46 -53
- jarvis/jarvis_methodology/main.py +4 -2
- jarvis/jarvis_platform/base.py +49 -12
- jarvis/jarvis_platform/kimi.py +16 -22
- jarvis/jarvis_platform/registry.py +7 -14
- jarvis/jarvis_platform/tongyi.py +21 -32
- jarvis/jarvis_platform/yuanbao.py +15 -17
- jarvis/jarvis_platform_manager/main.py +14 -51
- jarvis/jarvis_rag/cli.py +14 -13
- jarvis/jarvis_rag/embedding_manager.py +18 -6
- jarvis/jarvis_rag/llm_interface.py +0 -2
- jarvis/jarvis_rag/rag_pipeline.py +20 -13
- jarvis/jarvis_rag/retriever.py +21 -23
- jarvis/jarvis_tools/cli/main.py +22 -15
- jarvis/jarvis_tools/file_analyzer.py +12 -6
- jarvis/jarvis_tools/registry.py +13 -10
- jarvis/jarvis_tools/sub_agent.py +1 -1
- jarvis/jarvis_tools/sub_code_agent.py +1 -4
- jarvis/jarvis_utils/config.py +14 -10
- jarvis/jarvis_utils/input.py +6 -3
- jarvis/jarvis_utils/methodology.py +11 -6
- jarvis/jarvis_utils/utils.py +30 -13
- {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.24.dist-info}/METADATA +10 -3
- {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.24.dist-info}/RECORD +39 -39
- {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.24.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.24.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.24.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.23.dist-info → jarvis_ai_assistant-0.3.24.dist-info}/top_level.txt +0 -0
@@ -11,8 +11,6 @@ import typer
|
|
11
11
|
from jarvis.jarvis_utils.config import (
|
12
12
|
get_normal_platform_name,
|
13
13
|
get_normal_model_name,
|
14
|
-
get_thinking_platform_name,
|
15
|
-
get_thinking_model_name,
|
16
14
|
)
|
17
15
|
|
18
16
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
@@ -66,7 +64,7 @@ def list_platforms(
|
|
66
64
|
|
67
65
|
|
68
66
|
def chat_with_model(
|
69
|
-
platform_name: str, model_name: str, system_prompt: str
|
67
|
+
platform_name: str, model_name: str, system_prompt: str
|
70
68
|
) -> None:
|
71
69
|
"""与指定平台和模型进行对话。
|
72
70
|
|
@@ -74,7 +72,7 @@ def chat_with_model(
|
|
74
72
|
platform_name: 平台名称
|
75
73
|
model_name: 模型名称
|
76
74
|
system_prompt: 系统提示语
|
77
|
-
|
75
|
+
|
78
76
|
"""
|
79
77
|
registry = PlatformRegistry.get_global_platform_registry()
|
80
78
|
conversation_history: List[Dict[str, str]] = [] # 存储对话记录
|
@@ -360,32 +358,19 @@ def chat_command(
|
|
360
358
|
None, "--platform", "-p", help="指定要使用的平台"
|
361
359
|
),
|
362
360
|
model: Optional[str] = typer.Option(None, "--model", "-m", help="指定要使用的模型"),
|
363
|
-
|
364
|
-
"normal",
|
365
|
-
"-t",
|
366
|
-
"--llm-type",
|
367
|
-
help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
|
368
|
-
),
|
361
|
+
|
369
362
|
llm_group: Optional[str] = typer.Option(
|
370
363
|
None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
|
371
364
|
),
|
372
365
|
) -> None:
|
373
366
|
"""与指定平台和模型聊天。"""
|
374
367
|
# 如果未提供平台或模型参数,则从config获取默认值
|
375
|
-
platform = platform or (
|
376
|
-
|
377
|
-
if llm_type == "thinking"
|
378
|
-
else get_normal_platform_name(llm_group)
|
379
|
-
)
|
380
|
-
model = model or (
|
381
|
-
get_thinking_model_name(llm_group)
|
382
|
-
if llm_type == "thinking"
|
383
|
-
else get_normal_model_name(llm_group)
|
384
|
-
)
|
368
|
+
platform = platform or get_normal_platform_name(llm_group)
|
369
|
+
model = model or get_normal_model_name(llm_group)
|
385
370
|
|
386
371
|
if not validate_platform_model(platform, model):
|
387
372
|
return
|
388
|
-
chat_with_model(platform, model, ""
|
373
|
+
chat_with_model(platform, model, "")
|
389
374
|
|
390
375
|
|
391
376
|
@app.command("service")
|
@@ -444,12 +429,7 @@ def role_command(
|
|
444
429
|
model: Optional[str] = typer.Option(
|
445
430
|
None, "--model", "-m", help="指定要使用的模型,覆盖角色配置"
|
446
431
|
),
|
447
|
-
|
448
|
-
None,
|
449
|
-
"-t",
|
450
|
-
"--llm-type",
|
451
|
-
help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式),覆盖角色配置",
|
452
|
-
),
|
432
|
+
|
453
433
|
llm_group: Optional[str] = typer.Option(
|
454
434
|
None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
|
455
435
|
),
|
@@ -483,54 +463,37 @@ def role_command(
|
|
483
463
|
PrettyOutput.print("无效的选择", OutputType.ERROR)
|
484
464
|
return
|
485
465
|
|
486
|
-
|
487
|
-
role_llm_type = llm_type or selected_role.get("llm_type", "normal")
|
466
|
+
|
488
467
|
|
489
468
|
# 初始化平台和模型
|
490
469
|
# 如果提供了platform或model参数,优先使用命令行参数
|
491
|
-
# 否则,如果提供了llm_group
|
470
|
+
# 否则,如果提供了 llm_group,则从配置中获取
|
492
471
|
# 最后才使用角色配置中的platform和model
|
493
472
|
if platform:
|
494
473
|
platform_name = platform
|
495
474
|
elif llm_group:
|
496
|
-
platform_name = (
|
497
|
-
get_thinking_platform_name(llm_group)
|
498
|
-
if role_llm_type == "thinking"
|
499
|
-
else get_normal_platform_name(llm_group)
|
500
|
-
)
|
475
|
+
platform_name = get_normal_platform_name(llm_group)
|
501
476
|
else:
|
502
477
|
platform_name = selected_role.get("platform")
|
503
478
|
if not platform_name:
|
504
479
|
# 如果角色配置中没有platform,使用默认配置
|
505
|
-
platform_name = (
|
506
|
-
get_thinking_platform_name()
|
507
|
-
if role_llm_type == "thinking"
|
508
|
-
else get_normal_platform_name()
|
509
|
-
)
|
480
|
+
platform_name = get_normal_platform_name()
|
510
481
|
|
511
482
|
if model:
|
512
483
|
model_name = model
|
513
484
|
elif llm_group:
|
514
|
-
model_name = (
|
515
|
-
get_thinking_model_name(llm_group)
|
516
|
-
if role_llm_type == "thinking"
|
517
|
-
else get_normal_model_name(llm_group)
|
518
|
-
)
|
485
|
+
model_name = get_normal_model_name(llm_group)
|
519
486
|
else:
|
520
487
|
model_name = selected_role.get("model")
|
521
488
|
if not model_name:
|
522
489
|
# 如果角色配置中没有model,使用默认配置
|
523
|
-
model_name = (
|
524
|
-
get_thinking_model_name()
|
525
|
-
if role_llm_type == "thinking"
|
526
|
-
else get_normal_model_name()
|
527
|
-
)
|
490
|
+
model_name = get_normal_model_name()
|
528
491
|
|
529
492
|
system_prompt = selected_role.get("system_prompt", "")
|
530
493
|
|
531
494
|
# 开始对话
|
532
495
|
PrettyOutput.print(f"已选择角色: {selected_role['name']}", OutputType.SUCCESS)
|
533
|
-
chat_with_model(platform_name, model_name, system_prompt
|
496
|
+
chat_with_model(platform_name, model_name, system_prompt)
|
534
497
|
|
535
498
|
|
536
499
|
def main() -> None:
|
jarvis/jarvis_rag/cli.py
CHANGED
@@ -240,6 +240,7 @@ def add_documents(
|
|
240
240
|
|
241
241
|
sorted_files = sorted(list(files_to_process))
|
242
242
|
total_files = len(sorted_files)
|
243
|
+
loaded_msgs: List[str] = []
|
243
244
|
|
244
245
|
for i, file_path in enumerate(sorted_files):
|
245
246
|
try:
|
@@ -249,14 +250,15 @@ def add_documents(
|
|
249
250
|
loader = TextLoader(str(file_path), encoding="utf-8")
|
250
251
|
|
251
252
|
docs_batch.extend(loader.load())
|
252
|
-
|
253
|
-
f"已加载: {file_path} (文件 {i + 1}/{total_files})", OutputType.INFO
|
254
|
-
)
|
253
|
+
loaded_msgs.append(f"已加载: {file_path} (文件 {i + 1}/{total_files})")
|
255
254
|
except Exception as e:
|
256
255
|
PrettyOutput.print(f"加载失败 {file_path}: {e}", OutputType.WARNING)
|
257
256
|
|
258
257
|
# 当批处理已满或是最后一个文件时处理批处理
|
259
258
|
if docs_batch and (len(docs_batch) >= batch_size or (i + 1) == total_files):
|
259
|
+
if loaded_msgs:
|
260
|
+
PrettyOutput.print("\n".join(loaded_msgs), OutputType.INFO)
|
261
|
+
loaded_msgs = []
|
260
262
|
PrettyOutput.print(
|
261
263
|
f"正在处理批次,包含 {len(docs_batch)} 个文档...", OutputType.INFO
|
262
264
|
)
|
@@ -267,6 +269,10 @@ def add_documents(
|
|
267
269
|
)
|
268
270
|
docs_batch = [] # 清空批处理
|
269
271
|
|
272
|
+
# 最后统一打印可能残留的“已加载”信息
|
273
|
+
if loaded_msgs:
|
274
|
+
PrettyOutput.print("\n".join(loaded_msgs), OutputType.INFO)
|
275
|
+
loaded_msgs = []
|
270
276
|
if total_docs_added == 0:
|
271
277
|
PrettyOutput.print("未能成功加载任何文档。", OutputType.ERROR)
|
272
278
|
raise typer.Exit(code=1)
|
@@ -321,12 +327,11 @@ def list_documents(
|
|
321
327
|
)
|
322
328
|
return
|
323
329
|
|
324
|
-
|
325
|
-
|
326
|
-
OutputType.INFO,
|
327
|
-
)
|
330
|
+
# 避免在循环中逐条打印,先拼接后统一打印
|
331
|
+
lines = [f"知识库 '{collection_name}' 中共有 {len(sources)} 个独立文档:"]
|
328
332
|
for i, source in enumerate(sorted(list(sources)), 1):
|
329
|
-
|
333
|
+
lines.append(f" {i}. {source}")
|
334
|
+
PrettyOutput.print("\n".join(lines), OutputType.INFO)
|
330
335
|
|
331
336
|
except Exception as e:
|
332
337
|
PrettyOutput.print(f"发生错误: {e}", OutputType.ERROR)
|
@@ -450,11 +455,7 @@ def query(
|
|
450
455
|
PrettyOutput.print(f"正在查询: '{question}'", OutputType.INFO)
|
451
456
|
answer = pipeline.query(question)
|
452
457
|
|
453
|
-
PrettyOutput.print(
|
454
|
-
# 我们仍然可以使用 rich.markdown.Markdown,因为 PrettyOutput 底层使用了 rich
|
455
|
-
from jarvis.jarvis_utils.globals import console
|
456
|
-
|
457
|
-
console.print(Markdown(answer))
|
458
|
+
PrettyOutput.print(answer, OutputType.SUCCESS)
|
458
459
|
|
459
460
|
except Exception as e:
|
460
461
|
PrettyOutput.print(f"发生错误: {e}", OutputType.ERROR)
|
@@ -38,12 +38,24 @@ class EmbeddingManager:
|
|
38
38
|
encode_kwargs = {"normalize_embeddings": True}
|
39
39
|
|
40
40
|
try:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
41
|
+
# First try to load model locally
|
42
|
+
try:
|
43
|
+
from sentence_transformers import SentenceTransformer
|
44
|
+
local_model = SentenceTransformer(self.model_name, device=model_kwargs["device"])
|
45
|
+
return HuggingFaceEmbeddings(
|
46
|
+
client=local_model,
|
47
|
+
model_name=self.model_name,
|
48
|
+
model_kwargs=model_kwargs,
|
49
|
+
encode_kwargs=encode_kwargs,
|
50
|
+
)
|
51
|
+
except Exception:
|
52
|
+
# Fall back to remote download if local loading fails
|
53
|
+
return HuggingFaceEmbeddings(
|
54
|
+
model_name=self.model_name,
|
55
|
+
model_kwargs=model_kwargs,
|
56
|
+
encode_kwargs=encode_kwargs,
|
57
|
+
show_progress=True,
|
58
|
+
)
|
47
59
|
except Exception as e:
|
48
60
|
PrettyOutput.print(
|
49
61
|
f"加载嵌入模型 '{self.model_name}' 时出错: {e}", OutputType.ERROR
|
@@ -47,13 +47,11 @@ class ToolAgent_LLM(LLMInterface):
|
|
47
47
|
# 为代理提供一个通用的系统提示
|
48
48
|
self.system_prompt = "You are a helpful assistant. Please answer the user's question based on the provided context. You can use tools to find more information if needed."
|
49
49
|
self.summary_prompt = """
|
50
|
-
<report>
|
51
50
|
请为本次问答任务生成一个总结报告,包含以下内容:
|
52
51
|
|
53
52
|
1. **原始问题**: 重述用户最开始提出的问题。
|
54
53
|
2. **关键信息来源**: 总结你是基于哪些关键信息或文件得出的结论。
|
55
54
|
3. **最终答案**: 给出最终的、精炼的回答。
|
56
|
-
</report>
|
57
55
|
"""
|
58
56
|
|
59
57
|
def generate(self, prompt: str, **kwargs) -> str:
|
@@ -161,14 +161,15 @@ class JarvisRAGPipeline:
|
|
161
161
|
if not changed and not deleted:
|
162
162
|
return
|
163
163
|
# 打印摘要
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
164
|
+
# 先拼接列表信息再统一打印,避免循环中逐条打印
|
165
|
+
lines = [
|
166
|
+
f"检测到索引可能不一致:变更 {len(changed)} 个,删除 {len(deleted)} 个。"
|
167
|
+
]
|
168
|
+
if changed:
|
169
|
+
lines.extend([f" 变更: {p}" for p in changed[:3]])
|
170
|
+
if deleted:
|
171
|
+
lines.extend([f" 删除: {p}" for p in deleted[:3]])
|
172
|
+
PrettyOutput.print("\n".join(lines), OutputType.WARNING)
|
172
173
|
# 询问用户
|
173
174
|
if get_yes_no(
|
174
175
|
"检测到索引变更,是否现在更新索引后再开始检索?", default=True
|
@@ -232,9 +233,12 @@ class JarvisRAGPipeline:
|
|
232
233
|
rewritten_queries = self._get_query_rewriter().rewrite(query_text)
|
233
234
|
|
234
235
|
# 2. 为每个重写的查询检索初始候选文档
|
236
|
+
PrettyOutput.print(
|
237
|
+
"将为以下查询变体进行混合检索:\n" + "\n".join([f" - {q}" for q in rewritten_queries]),
|
238
|
+
OutputType.INFO,
|
239
|
+
)
|
235
240
|
all_candidate_docs = []
|
236
241
|
for q in rewritten_queries:
|
237
|
-
PrettyOutput.print(f"正在为查询变体 '{q}' 进行混合检索...", OutputType.INFO)
|
238
242
|
candidates = self._get_retriever().retrieve(
|
239
243
|
q, n_results=n_results * 2, use_bm25=self.use_bm25
|
240
244
|
)
|
@@ -273,9 +277,9 @@ class JarvisRAGPipeline:
|
|
273
277
|
)
|
274
278
|
)
|
275
279
|
if sources:
|
276
|
-
|
277
|
-
for source in sources
|
278
|
-
|
280
|
+
# 合并来源列表后一次性打印,避免多次加框
|
281
|
+
lines = ["根据以下文档回答:"] + [f" - {source}" for source in sources]
|
282
|
+
PrettyOutput.print("\n".join(lines), OutputType.INFO)
|
279
283
|
|
280
284
|
# 4. 创建最终提示并生成答案
|
281
285
|
# 我们使用原始的query_text作为给LLM的最终提示
|
@@ -303,9 +307,12 @@ class JarvisRAGPipeline:
|
|
303
307
|
rewritten_queries = self._get_query_rewriter().rewrite(query_text)
|
304
308
|
|
305
309
|
# 2. 检索候选文档
|
310
|
+
PrettyOutput.print(
|
311
|
+
"将为以下查询变体进行混合检索:\n" + "\n".join([f" - {q}" for q in rewritten_queries]),
|
312
|
+
OutputType.INFO,
|
313
|
+
)
|
306
314
|
all_candidate_docs = []
|
307
315
|
for q in rewritten_queries:
|
308
|
-
PrettyOutput.print(f"正在为查询变体 '{q}' 进行混合检索...", OutputType.INFO)
|
309
316
|
candidates = self._get_retriever().retrieve(
|
310
317
|
q, n_results=n_results * 2, use_bm25=self.use_bm25
|
311
318
|
)
|
jarvis/jarvis_rag/retriever.py
CHANGED
@@ -184,32 +184,26 @@ class ChromaRetriever:
|
|
184
184
|
deleted = result["deleted"]
|
185
185
|
if not changed and not deleted:
|
186
186
|
return
|
187
|
+
# 为避免在循环中逐条打印,先拼接后统一打印
|
188
|
+
lines: list[str] = []
|
187
189
|
if changed:
|
188
|
-
|
189
|
-
f"检测到 {len(changed)} 个已索引文件发生变化,建议重新索引以保证检索准确性。"
|
190
|
-
OutputType.WARNING,
|
190
|
+
lines.append(
|
191
|
+
f"检测到 {len(changed)} 个已索引文件发生变化,建议重新索引以保证检索准确性。"
|
191
192
|
)
|
192
|
-
for p in changed[:5]
|
193
|
-
PrettyOutput.print(f" 变更: {p}", OutputType.WARNING)
|
193
|
+
lines.extend([f" 变更: {p}" for p in changed[:5]])
|
194
194
|
if len(changed) > 5:
|
195
|
-
|
196
|
-
f" ... 以及另外 {len(changed) - 5} 个文件", OutputType.WARNING
|
197
|
-
)
|
195
|
+
lines.append(f" ... 以及另外 {len(changed) - 5} 个文件")
|
198
196
|
if deleted:
|
199
|
-
|
200
|
-
f"检测到 {len(deleted)} 个已索引文件已被删除,建议清理并重新索引。"
|
201
|
-
OutputType.WARNING,
|
197
|
+
lines.append(
|
198
|
+
f"检测到 {len(deleted)} 个已索引文件已被删除,建议清理并重新索引。"
|
202
199
|
)
|
203
|
-
for p in deleted[:5]
|
204
|
-
PrettyOutput.print(f" 删除: {p}", OutputType.WARNING)
|
200
|
+
lines.extend([f" 删除: {p}" for p in deleted[:5]])
|
205
201
|
if len(deleted) > 5:
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
PrettyOutput.print(
|
210
|
-
"提示:请使用 'jarvis-rag add <路径>' 重新索引相关文件,以更新向量库与BM25索引。",
|
211
|
-
OutputType.INFO,
|
202
|
+
lines.append(f" ... 以及另外 {len(deleted) - 5} 个文件")
|
203
|
+
lines.append(
|
204
|
+
"提示:请使用 'jarvis-rag add <路径>' 重新索引相关文件,以更新向量库与BM25索引。"
|
212
205
|
)
|
206
|
+
PrettyOutput.print("\n".join(lines), OutputType.WARNING)
|
213
207
|
|
214
208
|
def detect_index_changes(self) -> Dict[str, List[str]]:
|
215
209
|
"""
|
@@ -253,14 +247,18 @@ class ChromaRetriever:
|
|
253
247
|
return
|
254
248
|
|
255
249
|
# 先处理删除
|
250
|
+
delete_errors: list[str] = []
|
256
251
|
for src in deleted:
|
257
252
|
try:
|
258
253
|
self.collection.delete(where={"source": src}) # type: ignore[arg-type]
|
259
254
|
except Exception as e:
|
260
|
-
|
255
|
+
delete_errors.append(f"删除源 '{src}' 时出错: {e}")
|
256
|
+
if delete_errors:
|
257
|
+
PrettyOutput.print("\n".join(delete_errors), OutputType.WARNING)
|
261
258
|
|
262
259
|
# 再处理变更(重建)
|
263
260
|
docs_to_add: List[Document] = []
|
261
|
+
rebuild_errors: list[str] = []
|
264
262
|
for src in changed:
|
265
263
|
try:
|
266
264
|
# 删除旧条目
|
@@ -275,9 +273,9 @@ class ChromaRetriever:
|
|
275
273
|
Document(page_content=content, metadata={"source": src})
|
276
274
|
)
|
277
275
|
except Exception as e:
|
278
|
-
|
279
|
-
|
280
|
-
|
276
|
+
rebuild_errors.append(f"重建源 '{src}' 内容时出错: {e}")
|
277
|
+
if rebuild_errors:
|
278
|
+
PrettyOutput.print("\n".join(rebuild_errors), OutputType.WARNING)
|
281
279
|
|
282
280
|
if docs_to_add:
|
283
281
|
try:
|
jarvis/jarvis_tools/cli/main.py
CHANGED
@@ -39,18 +39,22 @@ def list_tools(
|
|
39
39
|
)
|
40
40
|
else:
|
41
41
|
PrettyOutput.section("可用工具列表", OutputType.SYSTEM)
|
42
|
+
# 为避免 PrettyOutput 对每行加框造成信息稀疏,先拼接字符串再统一打印
|
43
|
+
lines = []
|
44
|
+
import json as _json # local import to ensure available
|
42
45
|
for tool in tools:
|
43
|
-
|
44
|
-
|
46
|
+
lines.append(f"\n{tool['name']}")
|
47
|
+
lines.append(f" 描述: {tool['description']}")
|
45
48
|
if detailed:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
_json.dumps(tool["parameters"], ensure_ascii=False, indent=2)
|
51
|
-
|
52
|
-
|
53
|
-
)
|
49
|
+
lines.append(" 参数:")
|
50
|
+
# 使用 Markdown 代码块统一展示参数
|
51
|
+
lines.append("```json")
|
52
|
+
try:
|
53
|
+
lines.append(_json.dumps(tool["parameters"], ensure_ascii=False, indent=2))
|
54
|
+
except Exception:
|
55
|
+
lines.append(str(tool.get("parameters")))
|
56
|
+
lines.append("```")
|
57
|
+
PrettyOutput.print("\n".join(lines), OutputType.INFO, lang="markdown")
|
54
58
|
|
55
59
|
|
56
60
|
@app.command("stat")
|
@@ -202,15 +206,18 @@ def call_tool(
|
|
202
206
|
missing_params = [p for p in required_params if p not in tool_args]
|
203
207
|
|
204
208
|
if missing_params:
|
205
|
-
|
206
|
-
f"错误: 缺少必需参数: {', '.join(missing_params)}", OutputType.ERROR
|
207
|
-
)
|
208
|
-
PrettyOutput.print("\n参数说明:", OutputType.INFO)
|
209
|
+
# 先拼接提示与参数说明,再统一打印,避免循环中逐条打印
|
209
210
|
params = tool_obj.parameters.get("properties", {})
|
211
|
+
lines = [
|
212
|
+
f"错误: 缺少必需参数: {', '.join(missing_params)}",
|
213
|
+
"",
|
214
|
+
"参数说明:",
|
215
|
+
]
|
210
216
|
for param_name in required_params:
|
211
217
|
param_info = params.get(param_name, {})
|
212
218
|
desc = param_info.get("description", "无描述")
|
213
|
-
|
219
|
+
lines.append(f" - {param_name}: {desc}")
|
220
|
+
PrettyOutput.print("\n".join(lines), OutputType.ERROR)
|
214
221
|
raise typer.Exit(code=1)
|
215
222
|
|
216
223
|
result = registry.execute_tool(tool_name, tool_args)
|
@@ -30,7 +30,7 @@ class FileAnalyzerTool:
|
|
30
30
|
|
31
31
|
@staticmethod
|
32
32
|
def check() -> bool:
|
33
|
-
return PlatformRegistry().
|
33
|
+
return PlatformRegistry().get_normal_platform().support_upload_files()
|
34
34
|
|
35
35
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
36
36
|
"""执行文件分析操作
|
@@ -45,25 +45,31 @@ class FileAnalyzerTool:
|
|
45
45
|
file_paths = args["file_paths"]
|
46
46
|
prompt = args["prompt"]
|
47
47
|
|
48
|
-
#
|
48
|
+
# 验证文件路径(先收集不存在的文件,统一打印一次)
|
49
49
|
valid_files = []
|
50
|
+
missing_files = []
|
50
51
|
for file_path in file_paths:
|
51
52
|
if os.path.exists(file_path):
|
52
53
|
valid_files.append(file_path)
|
53
54
|
else:
|
54
|
-
|
55
|
+
missing_files.append(file_path)
|
56
|
+
if missing_files:
|
57
|
+
PrettyOutput.print(
|
58
|
+
"以下文件不存在:\n" + "\n".join(f" - {p}" for p in missing_files),
|
59
|
+
OutputType.WARNING,
|
60
|
+
)
|
55
61
|
|
56
62
|
if not valid_files:
|
57
63
|
return {"success": False, "stdout": "", "stderr": "没有找到有效的文件"}
|
58
64
|
|
59
|
-
#
|
60
|
-
platform = PlatformRegistry().
|
65
|
+
# 创建平台实例
|
66
|
+
platform = PlatformRegistry().get_normal_platform()
|
61
67
|
|
62
68
|
if not platform:
|
63
69
|
return {
|
64
70
|
"success": False,
|
65
71
|
"stdout": "",
|
66
|
-
"stderr": "
|
72
|
+
"stderr": "无法创建平台实例",
|
67
73
|
}
|
68
74
|
|
69
75
|
# 设置系统消息
|
jarvis/jarvis_tools/registry.py
CHANGED
@@ -73,7 +73,7 @@ arguments:
|
|
73
73
|
|
74
74
|
<string_format>
|
75
75
|
# 📝 字符串参数格式
|
76
|
-
|
76
|
+
使用 |2 语法表示字符串参数,防止多行字符串行首空格引起歧义。
|
77
77
|
|
78
78
|
{ot("TOOL_CALL")}
|
79
79
|
want: 当前的git状态,期望获取xxx的提交记录
|
@@ -81,7 +81,7 @@ name: execute_script
|
|
81
81
|
|
82
82
|
arguments:
|
83
83
|
interpreter: bash
|
84
|
-
script_content: |
|
84
|
+
script_content: |
|
85
85
|
git status --porcelain
|
86
86
|
{ct("TOOL_CALL")}
|
87
87
|
</string_format>
|
@@ -98,7 +98,6 @@ arguments:
|
|
98
98
|
<common_errors>
|
99
99
|
# ⚠️ 常见错误
|
100
100
|
- 同时调用多个工具
|
101
|
-
- 字符串参数缺少 |2
|
102
101
|
- 假设工具结果
|
103
102
|
- 创建虚构对话
|
104
103
|
- 在没有所需信息的情况下继续
|
@@ -276,14 +275,17 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
276
275
|
# 如果配置了 use 列表,只保留列表中的工具
|
277
276
|
if use_list:
|
278
277
|
filtered_tools = {}
|
278
|
+
missing = []
|
279
279
|
for tool_name in use_list:
|
280
280
|
if tool_name in self.tools:
|
281
281
|
filtered_tools[tool_name] = self.tools[tool_name]
|
282
282
|
else:
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
)
|
283
|
+
missing.append(tool_name)
|
284
|
+
if missing:
|
285
|
+
PrettyOutput.print(
|
286
|
+
"警告: 配置的工具不存在: " + ", ".join(f"'{name}'" for name in missing),
|
287
|
+
OutputType.WARNING,
|
288
|
+
)
|
287
289
|
self.tools = filtered_tools
|
288
290
|
|
289
291
|
# 如果配置了 dont_use 列表,排除列表中的工具
|
@@ -315,14 +317,15 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
315
317
|
)
|
316
318
|
|
317
319
|
# 遍历目录中的所有.yaml文件
|
320
|
+
error_lines = []
|
318
321
|
for file_path in mcp_tools_dir.glob("*.yaml"):
|
319
322
|
try:
|
320
323
|
config = yaml.safe_load(open(file_path, "r", encoding="utf-8"))
|
321
324
|
self.register_mcp_tool_by_config(config)
|
322
325
|
except Exception as e:
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
+
error_lines.append(f"文件 {file_path} 加载失败: {str(e)}")
|
327
|
+
if error_lines:
|
328
|
+
PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
|
326
329
|
|
327
330
|
def _load_builtin_tools(self) -> None:
|
328
331
|
"""从内置工具目录加载工具"""
|
jarvis/jarvis_tools/sub_agent.py
CHANGED
@@ -133,7 +133,7 @@ class SubAgentTool:
|
|
133
133
|
system_prompt=system_prompt,
|
134
134
|
name="SubAgent",
|
135
135
|
description="Temporary sub agent for executing a subtask",
|
136
|
-
|
136
|
+
|
137
137
|
model_group=model_group, # 继承父Agent模型组(如可用)
|
138
138
|
summary_prompt=summary_prompt, # 继承父Agent总结提示词(如可用)
|
139
139
|
auto_complete=auto_complete,
|
@@ -103,12 +103,10 @@ class SubCodeAgentTool:
|
|
103
103
|
pass
|
104
104
|
|
105
105
|
# 创建 CodeAgent:参数优先使用父Agent的配置(若可获取),否则使用默认
|
106
|
-
# 推断/继承
|
107
|
-
llm_type = "normal"
|
106
|
+
# 推断/继承 tool_group
|
108
107
|
tool_group = None
|
109
108
|
try:
|
110
109
|
if parent_agent is not None:
|
111
|
-
llm_type = getattr(parent_agent, "llm_type", llm_type)
|
112
110
|
tool_group = getattr(parent_agent, "tool_group", tool_group)
|
113
111
|
except Exception:
|
114
112
|
pass
|
@@ -135,7 +133,6 @@ class SubCodeAgentTool:
|
|
135
133
|
|
136
134
|
try:
|
137
135
|
code_agent = CodeAgent(
|
138
|
-
llm_type=llm_type,
|
139
136
|
model_group=model_group,
|
140
137
|
need_summary=True,
|
141
138
|
append_tools=append_tools,
|
jarvis/jarvis_utils/config.py
CHANGED
@@ -149,8 +149,6 @@ def _get_resolved_model_config(
|
|
149
149
|
for key in [
|
150
150
|
"JARVIS_PLATFORM",
|
151
151
|
"JARVIS_MODEL",
|
152
|
-
"JARVIS_THINKING_PLATFORM",
|
153
|
-
"JARVIS_THINKING_MODEL",
|
154
152
|
"JARVIS_MAX_INPUT_TOKEN_COUNT",
|
155
153
|
]:
|
156
154
|
if key in GLOBAL_CONFIG_DATA:
|
@@ -181,7 +179,7 @@ def get_normal_model_name(model_group_override: Optional[str] = None) -> str:
|
|
181
179
|
return config.get("JARVIS_MODEL", "deep_seek_v3")
|
182
180
|
|
183
181
|
|
184
|
-
def
|
182
|
+
def _deprecated_platform_name_v1(model_group_override: Optional[str] = None) -> str:
|
185
183
|
"""
|
186
184
|
获取思考操作的平台名称。
|
187
185
|
|
@@ -190,12 +188,10 @@ def get_thinking_platform_name(model_group_override: Optional[str] = None) -> st
|
|
190
188
|
"""
|
191
189
|
config = _get_resolved_model_config(model_group_override)
|
192
190
|
# Fallback to normal platform if thinking platform is not specified
|
193
|
-
return
|
194
|
-
"JARVIS_THINKING_PLATFORM", get_normal_platform_name(model_group_override)
|
195
|
-
)
|
191
|
+
return get_normal_platform_name(model_group_override)
|
196
192
|
|
197
193
|
|
198
|
-
def
|
194
|
+
def _deprecated_model_name_v1(model_group_override: Optional[str] = None) -> str:
|
199
195
|
"""
|
200
196
|
获取思考操作的模型名称。
|
201
197
|
|
@@ -204,9 +200,7 @@ def get_thinking_model_name(model_group_override: Optional[str] = None) -> str:
|
|
204
200
|
"""
|
205
201
|
config = _get_resolved_model_config(model_group_override)
|
206
202
|
# Fallback to normal model if thinking model is not specified
|
207
|
-
return
|
208
|
-
"JARVIS_THINKING_MODEL", get_normal_model_name(model_group_override)
|
209
|
-
)
|
203
|
+
return get_normal_model_name(model_group_override)
|
210
204
|
|
211
205
|
|
212
206
|
def is_execute_tool_confirm() -> bool:
|
@@ -638,6 +632,16 @@ def get_tool_dont_use_list() -> List[str]:
|
|
638
632
|
return config.get("dont_use", [])
|
639
633
|
|
640
634
|
|
635
|
+
def get_tool_filter_threshold() -> int:
|
636
|
+
"""
|
637
|
+
获取AI工具筛选的阈值。
|
638
|
+
|
639
|
+
返回:
|
640
|
+
int: 当工具数量超过此阈值时,触发AI筛选。默认为30
|
641
|
+
"""
|
642
|
+
return int(GLOBAL_CONFIG_DATA.get("JARVIS_TOOL_FILTER_THRESHOLD", 30))
|
643
|
+
|
644
|
+
|
641
645
|
def is_enable_git_repo_jca_switch() -> bool:
|
642
646
|
"""
|
643
647
|
是否启用:在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)
|