jarvis-ai-assistant 0.3.29__py3-none-any.whl → 0.3.31__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 +116 -9
- jarvis/jarvis_agent/edit_file_handler.py +330 -83
- jarvis/jarvis_agent/run_loop.py +1 -3
- jarvis/jarvis_code_agent/code_agent.py +91 -8
- jarvis/jarvis_data/config_schema.json +46 -4
- jarvis/jarvis_tools/edit_file.py +39 -10
- jarvis/jarvis_tools/generate_new_tool.py +13 -2
- jarvis/jarvis_tools/read_code.py +12 -11
- jarvis/jarvis_tools/registry.py +16 -15
- jarvis/jarvis_utils/config.py +31 -0
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/git_utils.py +34 -18
- jarvis/jarvis_utils/utils.py +74 -18
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/RECORD +20 -20
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
@@ -4,6 +4,7 @@ import datetime
|
|
4
4
|
import os
|
5
5
|
import platform
|
6
6
|
import re
|
7
|
+
import sys
|
7
8
|
from pathlib import Path
|
8
9
|
from enum import Enum
|
9
10
|
from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
|
@@ -48,6 +49,7 @@ from jarvis.jarvis_agent.events import (
|
|
48
49
|
INTERRUPT_TRIGGERED,
|
49
50
|
BEFORE_TOOL_FILTER,
|
50
51
|
TOOL_FILTERED,
|
52
|
+
AFTER_TOOL_CALL,
|
51
53
|
)
|
52
54
|
from jarvis.jarvis_agent.user_interaction import UserInteractionHandler
|
53
55
|
from jarvis.jarvis_agent.utils import join_prompts
|
@@ -68,6 +70,7 @@ from jarvis.jarvis_utils.config import (
|
|
68
70
|
is_use_analysis,
|
69
71
|
is_use_methodology,
|
70
72
|
get_tool_filter_threshold,
|
73
|
+
get_after_tool_call_cb_dirs,
|
71
74
|
)
|
72
75
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
73
76
|
from jarvis.jarvis_utils.globals import (
|
@@ -310,7 +313,7 @@ class Agent:
|
|
310
313
|
self.first = True
|
311
314
|
self.run_input_handlers_next_turn = False
|
312
315
|
self.user_data: Dict[str, Any] = {}
|
313
|
-
|
316
|
+
|
314
317
|
|
315
318
|
# 用户确认回调:默认使用 CLI 的 user_confirm,可由外部注入以支持 TUI/GUI
|
316
319
|
self.user_confirm: Callable[[str, bool], bool] = (
|
@@ -361,6 +364,8 @@ class Agent:
|
|
361
364
|
self.get_tool_registry(), # type: ignore
|
362
365
|
platform_name=self.model.platform_name(), # type: ignore
|
363
366
|
)
|
367
|
+
# 动态加载工具调用后回调
|
368
|
+
self._load_after_tool_callbacks()
|
364
369
|
|
365
370
|
def _init_model(self, model_group: Optional[str]):
|
366
371
|
"""初始化模型平台(统一使用 normal 平台/模型)"""
|
@@ -502,13 +507,95 @@ class Agent:
|
|
502
507
|
# Fallback for custom handlers that only accept one argument
|
503
508
|
return self.multiline_inputer(tip) # type: ignore
|
504
509
|
|
505
|
-
def
|
506
|
-
"""设置工具调用后回调函数。
|
507
|
-
|
508
|
-
参数:
|
509
|
-
cb: 回调函数
|
510
|
+
def _load_after_tool_callbacks(self) -> None:
|
510
511
|
"""
|
511
|
-
|
512
|
+
扫描 JARVIS_AFTER_TOOL_CALL_CB_DIRS 中的 Python 文件并动态注册回调。
|
513
|
+
约定优先级(任一命中即注册):
|
514
|
+
- 模块级可调用对象: after_tool_call_cb
|
515
|
+
- 工厂方法返回单个或多个可调用对象: get_after_tool_call_cb(), register_after_tool_call_cb()
|
516
|
+
"""
|
517
|
+
try:
|
518
|
+
dirs = get_after_tool_call_cb_dirs()
|
519
|
+
if not dirs:
|
520
|
+
return
|
521
|
+
for d in dirs:
|
522
|
+
p_dir = Path(d)
|
523
|
+
if not p_dir.exists() or not p_dir.is_dir():
|
524
|
+
continue
|
525
|
+
for file_path in p_dir.glob("*.py"):
|
526
|
+
if file_path.name == "__init__.py":
|
527
|
+
continue
|
528
|
+
parent_dir = str(file_path.parent)
|
529
|
+
added_path = False
|
530
|
+
try:
|
531
|
+
if parent_dir not in sys.path:
|
532
|
+
sys.path.insert(0, parent_dir)
|
533
|
+
added_path = True
|
534
|
+
module_name = file_path.stem
|
535
|
+
module = __import__(module_name)
|
536
|
+
|
537
|
+
candidates: List[Callable[[Any], None]] = []
|
538
|
+
|
539
|
+
# 1) 直接导出的回调
|
540
|
+
if hasattr(module, "after_tool_call_cb"):
|
541
|
+
obj = getattr(module, "after_tool_call_cb")
|
542
|
+
if callable(obj):
|
543
|
+
candidates.append(obj) # type: ignore[arg-type]
|
544
|
+
|
545
|
+
# 2) 工厂方法:get_after_tool_call_cb()
|
546
|
+
if hasattr(module, "get_after_tool_call_cb"):
|
547
|
+
factory = getattr(module, "get_after_tool_call_cb")
|
548
|
+
if callable(factory):
|
549
|
+
try:
|
550
|
+
ret = factory()
|
551
|
+
if callable(ret):
|
552
|
+
candidates.append(ret)
|
553
|
+
elif isinstance(ret, (list, tuple)):
|
554
|
+
for c in ret:
|
555
|
+
if callable(c):
|
556
|
+
candidates.append(c)
|
557
|
+
except Exception:
|
558
|
+
pass
|
559
|
+
|
560
|
+
# 3) 工厂方法:register_after_tool_call_cb()
|
561
|
+
if hasattr(module, "register_after_tool_call_cb"):
|
562
|
+
factory2 = getattr(module, "register_after_tool_call_cb")
|
563
|
+
if callable(factory2):
|
564
|
+
try:
|
565
|
+
ret2 = factory2()
|
566
|
+
if callable(ret2):
|
567
|
+
candidates.append(ret2)
|
568
|
+
elif isinstance(ret2, (list, tuple)):
|
569
|
+
for c in ret2:
|
570
|
+
if callable(c):
|
571
|
+
candidates.append(c)
|
572
|
+
except Exception:
|
573
|
+
pass
|
574
|
+
|
575
|
+
for cb in candidates:
|
576
|
+
try:
|
577
|
+
def _make_wrapper(callback):
|
578
|
+
def _wrapper(**kwargs: Any) -> None:
|
579
|
+
try:
|
580
|
+
agent = kwargs.get("agent")
|
581
|
+
callback(agent)
|
582
|
+
except Exception:
|
583
|
+
pass
|
584
|
+
return _wrapper
|
585
|
+
self.event_bus.subscribe(AFTER_TOOL_CALL, _make_wrapper(cb))
|
586
|
+
except Exception:
|
587
|
+
pass
|
588
|
+
|
589
|
+
except Exception as e:
|
590
|
+
PrettyOutput.print(f"从 {file_path} 加载回调失败: {e}", OutputType.WARNING)
|
591
|
+
finally:
|
592
|
+
if added_path:
|
593
|
+
try:
|
594
|
+
sys.path.remove(parent_dir)
|
595
|
+
except ValueError:
|
596
|
+
pass
|
597
|
+
except Exception as e:
|
598
|
+
PrettyOutput.print(f"加载回调目录时发生错误: {e}", OutputType.WARNING)
|
512
599
|
|
513
600
|
def save_session(self) -> bool:
|
514
601
|
"""Saves the current session state by delegating to the session manager."""
|
@@ -642,7 +729,14 @@ class Agent:
|
|
642
729
|
pass
|
643
730
|
|
644
731
|
response = self.model.chat_until_success(message) # type: ignore
|
645
|
-
|
732
|
+
# 防御: 模型可能返回空响应(None或空字符串),统一为空字符串并告警
|
733
|
+
if not response:
|
734
|
+
try:
|
735
|
+
PrettyOutput.print("模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
736
|
+
except Exception:
|
737
|
+
pass
|
738
|
+
response = ""
|
739
|
+
|
646
740
|
# 事件:模型调用后
|
647
741
|
try:
|
648
742
|
self.event_bus.emit(
|
@@ -674,7 +768,13 @@ class Agent:
|
|
674
768
|
summary = self.model.chat_until_success(
|
675
769
|
self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
|
676
770
|
) # type: ignore
|
677
|
-
|
771
|
+
# 防御: 可能返回空响应(None或空字符串),统一为空字符串并告警
|
772
|
+
if not summary:
|
773
|
+
try:
|
774
|
+
PrettyOutput.print("总结模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
775
|
+
except Exception:
|
776
|
+
pass
|
777
|
+
summary = ""
|
678
778
|
return summary
|
679
779
|
except Exception:
|
680
780
|
PrettyOutput.print("总结对话历史失败", OutputType.ERROR)
|
@@ -811,6 +911,13 @@ class Agent:
|
|
811
911
|
if not self.model:
|
812
912
|
raise RuntimeError("Model not initialized")
|
813
913
|
ret = self.model.chat_until_success(self.session.prompt) # type: ignore
|
914
|
+
# 防御: 总结阶段模型可能返回空响应(None或空字符串),统一为空字符串并告警
|
915
|
+
if not ret:
|
916
|
+
try:
|
917
|
+
PrettyOutput.print("总结阶段模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
918
|
+
except Exception:
|
919
|
+
pass
|
920
|
+
ret = ""
|
814
921
|
result = ret
|
815
922
|
|
816
923
|
# 广播完成总结事件
|