feat(agents): Phase 6 tool system refactoring

Phase 6.1: ToolRegistry infrastructure
- Add ToolManifest with ToolCategory, PermissionClass, SideEffectScope
- Add ToolRegistry singleton with register/get/unregister/list/search
- Add BaseTool abstract class with ReadTool/WriteTool/DBWriteTool/ExternalTool/NetworkTool subclasses
- Add migration layer for backward compatibility

Phase 6.2: Hook interception system
- Add HookType (PRE_TOOL_USE, POST_TOOL_USE, TOOL_ERROR, TOOL_SKIP)
- Add HookManager with singleton for hook registration
- Add HookExecutor for pre/post/error hook execution

Phase 6.3: Streaming execution
- Add StreamingToolExecutor with batch execution support

Phase 6.4: New builtin tools
- Add file_tools: GlobTool, GrepTool, ReadFileTool, WriteFileTool
- Add system_tools: BashTool, PowerShellTool
- Add dev_tools: LSPTools, GitTool
- Add collaboration_tools: TeamAgentTool, TaskBroadcastTool

Tests: 29 passed
This commit is contained in:
2026-04-04 22:47:48 +08:00
parent a7b6b5eb90
commit e5bd492d74
16 changed files with 2541 additions and 2 deletions

View File

@@ -0,0 +1,46 @@
"""Hook 系统 - Phase 6.2"""
from app.agents.tools.hooks.types import (
HookDefinition,
HookResult,
HookStage,
HookTrigger,
HookType,
ExecutionContext,
HookHandler,
PreToolHook,
PostToolHook,
ErrorToolHook,
SkipToolHook,
)
from app.agents.tools.hooks.manager import (
HookManager,
get_hook_manager,
reset_hook_manager,
)
from app.agents.tools.hooks.executor import (
HookExecutor,
get_hook_executor,
)
__all__ = [
# Types
"HookType",
"HookStage",
"HookTrigger",
"HookDefinition",
"HookResult",
"ExecutionContext",
"HookHandler",
"PreToolHook",
"PostToolHook",
"ErrorToolHook",
"SkipToolHook",
# Manager
"HookManager",
"get_hook_manager",
"reset_hook_manager",
# Executor
"HookExecutor",
"get_hook_executor",
]

View File

@@ -0,0 +1,170 @@
"""Hook 执行器 - Phase 6.2
执行 Hook 拦截逻辑。
"""
import time
from typing import Any
from app.agents.tools.hooks.manager import get_hook_manager
from app.agents.tools.hooks.types import (
HookDefinition,
HookResult,
HookType,
ExecutionContext,
)
class HookExecutor:
"""Hook 执行器
负责在工具执行前后执行 Hook 逻辑。
"""
def __init__(self):
self._manager = get_hook_manager()
async def execute_pre_hooks(
self, context: ExecutionContext
) -> tuple[bool, dict[str, Any] | None]:
"""执行 pre-tool Hook
Args:
context: 执行上下文
Returns:
(是否继续执行, 修改后的输入)
"""
hooks = self._manager.get_hooks(HookType.PRE_TOOL_USE, context.tool_name)
modified_input = context.tool_input
for hook in hooks:
try:
# 调用 hook handler
handler = hook.handler
if callable(handler):
result = await self._call_hook(handler, context)
if result and not result.continue_execution:
# Hook 决定中断执行
return False, modified_input
if result.modified_input is not None:
modified_input = result.modified_input
except Exception as e:
# Hook 出错,默认继续执行
pass
return True, modified_input
async def execute_post_hooks(self, context: ExecutionContext, result: Any) -> Any:
"""执行 post-tool Hook
Args:
context: 执行上下文
result: 工具执行结果
Returns:
修改后的结果
"""
hooks = self._manager.get_hooks(HookType.POST_TOOL_USE, context.tool_name)
modified_result = result
for hook in hooks:
try:
handler = hook.handler
if callable(handler):
hook_result = await self._call_hook(handler, context, modified_result)
if hook_result and hook_result.modified_output is not None:
modified_result = hook_result.modified_output
except Exception:
# Hook 出错,默认保留原结果
pass
return modified_result
async def execute_error_hooks(
self, context: ExecutionContext, error: Exception
) -> HookResult | None:
"""执行 error Hook
Args:
context: 执行上下文
error: 异常
Returns:
Hook 结果,如果返回 None 则继续传播错误
"""
hooks = self._manager.get_hooks(HookType.TOOL_ERROR, context.tool_name)
for hook in hooks:
try:
handler = hook.handler
if callable(handler):
result = await self._call_hook(handler, context, error)
if result is not None and result.continue_execution:
return result
except Exception:
# Hook 出错,继续执行其他 error hooks
pass
return None
async def execute_skip_check(self, context: ExecutionContext) -> bool:
"""检查是否应跳过工具执行
Args:
context: 执行上下文
Returns:
True 表示跳过False 表示执行
"""
hooks = self._manager.get_hooks(HookType.TOOL_SKIP, context.tool_name)
for hook in hooks:
try:
handler = hook.handler
if callable(handler):
result = await self._call_hook(handler, context)
if result is not None and isinstance(result, bool):
return result
except Exception:
# Hook 出错,默认不跳过
pass
return False
async def _call_hook(
self, handler: Any, context: ExecutionContext, *args: Any
) -> HookResult | None:
"""调用 Hook 处理函数
Args:
handler: Hook 处理函数
context: 执行上下文
*args: 额外参数
Returns:
Hook 结果
"""
import asyncio
# 如果是普通函数,直接调用
if asyncio.iscoroutinefunction(handler):
return await handler(context, *args)
else:
return handler(context, *args)
# 全局单例
_executor: HookExecutor | None = None
def get_hook_executor() -> HookExecutor:
"""获取全局 Hook 执行器
Returns:
全局 HookExecutor 实例
"""
global _executor
if _executor is None:
_executor = HookExecutor()
return _executor

View File

@@ -0,0 +1,174 @@
"""Hook 管理器 - Phase 6.2
管理 Hook 的注册、查找和配置。
"""
from typing import Any
from app.agents.tools.hooks.types import (
HookDefinition,
HookResult,
HookTrigger,
HookType,
ExecutionContext,
)
class HookManager:
"""Hook 管理器
管理全局 Hook 的注册和配置。
"""
def __init__(self):
self._hooks: dict[HookType, list[HookDefinition]] = {
HookType.PRE_TOOL_USE: [],
HookType.POST_TOOL_USE: [],
HookType.TOOL_ERROR: [],
HookType.TOOL_SKIP: [],
}
self._global_hooks: list[HookDefinition] = [] # 全局 Hook对所有工具生效
def register(self, definition: HookDefinition) -> None:
"""注册 Hook
Args:
definition: Hook 定义
"""
if definition.trigger.tool_names is None and definition.trigger.categories is None:
# 全局 Hook
self._global_hooks.append(definition)
else:
# 特定工具 Hook
self._hooks[definition.hook_type].append(definition)
# 按优先级排序
self._hooks[definition.hook_type].sort(key=lambda h: h.priority, reverse=True)
self._global_hooks.sort(key=lambda h: h.priority, reverse=True)
def unregister(self, name: str) -> bool:
"""注销 Hook
Args:
name: Hook 名称
Returns:
是否成功注销
"""
# 从特定工具 Hook 中移除
for hooks in self._hooks.values():
for i, hook in enumerate(hooks):
if hook.name == name:
hooks.pop(i)
return True
# 从全局 Hook 中移除
for i, hook in enumerate(self._global_hooks):
if hook.name == name:
self._global_hooks.pop(i)
return True
return False
def get_hooks(self, hook_type: HookType, tool_name: str | None = None) -> list[HookDefinition]:
"""获取指定类型和工具的 Hook
Args:
hook_type: Hook 类型
tool_name: 工具名称(可选)
Returns:
匹配的 Hook 列表
"""
result: list[HookDefinition] = []
# 添加全局 Hook
for hook in self._global_hooks:
if hook.hook_type == hook_type and hook.enabled:
result.append(hook)
# 添加特定工具 Hook
for hook in self._hooks[hook_type]:
if not hook.enabled:
continue
if hook.trigger.tool_names is None and hook.trigger.categories is None:
continue
# 检查是否匹配
if hook.trigger.tool_names and tool_name not in hook.trigger.tool_names:
continue
result.append(hook)
return result
def list_all(self) -> list[HookDefinition]:
"""列出所有已注册的 Hook
Returns:
Hook 列表
"""
all_hooks = list(self._global_hooks)
for hooks in self._hooks.values():
all_hooks.extend(hooks)
return all_hooks
def enable(self, name: str) -> bool:
"""启用 Hook
Args:
name: Hook 名称
Returns:
是否成功启用
"""
for hook in self.list_all():
if hook.name == name:
hook.enabled = True
return True
return False
def disable(self, name: str) -> bool:
"""禁用 Hook
Args:
name: Hook 名称
Returns:
是否成功禁用
"""
for hook in self.list_all():
if hook.name == name:
hook.enabled = False
return True
return False
def clear(self) -> None:
"""清除所有 Hook"""
self._hooks = {ht: [] for ht in HookType}
self._global_hooks = []
# 全局单例
_global_hook_manager: HookManager | None = None
def get_hook_manager() -> HookManager:
"""获取全局 Hook 管理器
Returns:
全局 HookManager 实例
"""
global _global_hook_manager
if _global_hook_manager is None:
_global_hook_manager = HookManager()
return _global_hook_manager
def reset_hook_manager() -> None:
"""重置全局 Hook 管理器(用于测试)"""
global _global_hook_manager
if _global_hook_manager is not None:
_global_hook_manager.clear()
_global_hook_manager = None

View File

@@ -0,0 +1,90 @@
"""Hook 类型定义 - Phase 6.2
Hook 拦截系统类型定义。
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable
class HookType(Enum):
"""Hook 类型"""
PRE_TOOL_USE = "pre_tool_use" # 工具执行前
POST_TOOL_USE = "post_tool_use" # 工具执行后
TOOL_ERROR = "tool_error" # 工具执行出错
TOOL_SKIP = "tool_skip" # 工具跳过(条件执行)
class HookStage(Enum):
"""Hook 执行阶段"""
BEFORE = "before"
AFTER = "after"
ON_ERROR = "on_error"
@dataclass
class HookTrigger:
"""Hook 触发条件"""
tool_names: list[str] | None = None # 只对特定工具生效None 表示全部
categories: list[str] | None = None # 只对特定类别生效
conditions: dict[str, Any] | None = None # 自定义条件
@dataclass
class HookDefinition:
"""Hook 定义"""
name: str
hook_type: HookType
trigger: HookTrigger
handler: Callable[..., Any] # Hook 处理函数
priority: int = 0 # 优先级,数字越大越先执行
enabled: bool = True
description: str = ""
@dataclass
class HookResult:
"""Hook 执行结果"""
hook_name: str
success: bool
continue_execution: bool = True # False 表示中断执行
modified_input: Any = None # 修改后的输入
modified_output: Any = None # 修改后的输出
error: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class ExecutionContext:
"""工具执行上下文"""
tool_name: str
tool_input: dict[str, Any]
user_id: str | None = None
session_id: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
# 执行结果(由 HookExecutor 填充)
result: Any = None
error: Exception | None = None
start_time: float | None = None
end_time: float | None = None
# Hook 处理函数类型
HookHandler = Callable[[ExecutionContext, HookDefinition], HookResult]
# Pre-hook: 在工具执行前调用,可以修改输入或决定是否跳过
PreToolHook = Callable[[ExecutionContext], tuple[bool, dict[str, Any] | None]]
# post-hook: 在工具执行后调用,可以修改输出
PostToolHook = Callable[[ExecutionContext, Any], Any]
# Error hook: 在工具出错时调用
ErrorToolHook = Callable[[ExecutionContext, Exception], HookResult | None]
# Skip hook: 决定是否跳过工具执行
SkipToolHook = Callable[[ExecutionContext], bool]