♻️ refactor(core): 重构 AI 能力与定时任务调度系统 (#2148)

* ♻️ refactor(core): 重构 AI 能力与定时任务调度系统

- 【AI 能力与工具】重构 Capability 注册与管理机制,引入 CapabilityManager 统一管理
- 移除全局能力注册表,改用声明式装饰器 `@capability` 进行解耦注册
- 重构工具解析器链,使用统一的 BaseToolResolver 代替原有的多个特定解析器
- 增强工具查询过滤,支持通配符匹配、工具箱过滤和排除标签
- 【定时任务调度】重构定时任务管理器,引入 SchedulerRegistry 统一管理任务元数据
- 引入 JobConfig 聚合定时任务配置,支持用户维度的定时任务调度
- 重构执行分发器,支持并发限制、串行间隔和随机延迟打散
- 【运行上下文】引入 ScheduledDeps 以支持后台和定时任务环境下的依赖注入
- 优化 RunContext,支持从定时任务上下文快速构造,并提供 emit 辅助方法
- 【日志与监控】引入 AILoggerProxy,实现 AI 各模块的专属日志输出
- 将各模块的全局 logger 替换为对应的模块专属日志代理
- 【其他优化】修复 Pydantic V1 兼容层中 model_validator 的装饰器兼容性问题
- 在非交互式环境(如定时任务)中自动隐藏 HITL 交互工具以节省 Token

* ♻️ refactor(core): 优化内部导入路径并提升 Pydantic 兼容性

- 【重构】将 `services/ai` 模块内的绝对导入重构为相对导入,优化包结构
- 【重构】移除不必要的 `if TYPE_CHECKING` 保护,通过 `from __future__ import annotations` 直接导入类型
- 【清理】清理 `core/messages/types.py` 中未使用的 `AssistantContentUnion` 等联合类型定义
- 【优化】在 `utils/pydantic_compat.py` 中新增 `model_rebuild` 兼容函数,统一 Pydantic V1/V2 的模型重建逻辑
- 【优化】将部分函数内部的延迟导入提升至模块顶部,规范代码结构

* ♻️ refactor(imports): 优化导入路径为相对导入并清理冗余导入

- 【重构】将 AI 服务相关模块中的绝对导入路径修改为相对导入,提升模块内聚性与可移植性
- 【清理】移除多处函数内部或类方法中未使用的冗余导入,避免循环引用和资源浪费
- 【格式化】微调部分工具装饰器和返回语句的格式与尾随逗号

* 🚨 auto fix by pre-commit hooks

---------

Co-authored-by: webjoin111 <455457521@qq.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Rumio
2026-07-10 09:14:06 +08:00
committed by GitHub
co-authored by webjoin111 pre-commit-ci[bot]
parent 0b32d69c9c
commit 922d092650
132 changed files with 2372 additions and 1972 deletions
+78 -36
View File
@@ -4,17 +4,31 @@ from contextlib import contextmanager
from contextvars import ContextVar
import dataclasses
import inspect
from typing import Any, Generic, cast, get_origin
from typing import TYPE_CHECKING, Any, Generic, cast, get_origin
from typing_extensions import TypeVar
import uuid
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot.matcher import Matcher, current_bot, current_event, current_matcher
from pydantic import BaseModel, ConfigDict, Field
from zhenxun.services.ai.capabilities.base import AbstractCapability
from zhenxun.services.ai.core.engine.append_only import AppendOnlyContextManager
from zhenxun.services.ai.core.messages import AgentEvent, AgentMessage
from zhenxun.services.ai.core.models import CancellationToken
from zhenxun.services.ai.core.protocols.tool import ToolExecutable
from zhenxun.services.ai.core.stream_events import AgentStreamEvent, EventBus
from zhenxun.services.ai.utils import ContextUtils
from zhenxun.services.ai.utils.scope import ScopeSelector
from zhenxun.services.scheduler.types import ScheduleContext
from zhenxun.utils.platform import PlatformUtils
from zhenxun.utils.utils import infer_plugin_namespace
from .blackboard import BlackboardManager
if TYPE_CHECKING:
from zhenxun.services.ai.context.memory.facades import AgentSessionFacade
AgentDepsT = TypeVar("AgentDepsT", default=Any)
"""泛型类型变量:外部环境依赖对象 (Agent Dependencies)。"""
ProviderFunc = Callable[["RunContext"], Any | Awaitable[Any]]
@@ -40,19 +54,47 @@ class NoneBotDeps(BaseModel):
@classmethod
def get_current(cls) -> "NoneBotDeps | None":
"""利用 NoneBot 原生魔法,基于 ContextVars 隐式提取当前执行上下文"""
try:
from nonebot.matcher import current_bot, current_event, current_matcher
bot = current_bot.get(None)
event = current_event.get(None)
matcher = current_matcher.get(None)
if bot or event:
return cls(bot=bot, event=event, matcher=matcher)
bot = current_bot.get(None)
event = current_event.get(None)
matcher = current_matcher.get(None)
if bot or event:
return cls(bot=bot, event=event, matcher=matcher)
except Exception:
pass
return None
class ScheduledDeps(BaseModel):
"""后台/定时任务环境下的标准依赖容器。"""
model_config = ConfigDict(arbitrary_types_allowed=True)
bot: Bot | None = Field(default=None)
"""机器人实例"""
group_id: str | None = Field(default=None)
"""群组/频道 ID"""
user_id: str | None = Field(default=None)
"""用户 ID"""
schedule_id: int | str | None = Field(default=None)
"""定时任务/调度任务 ID"""
platform: str | None = Field(default=None)
"""适配器平台标识"""
@classmethod
def from_schedule_context(
cls, bot: Bot, context: ScheduleContext
) -> "ScheduledDeps":
"""从调度器上下文中快速构造并提取依赖"""
return cls(
bot=bot,
group_id=getattr(context, "group_id", None),
user_id=getattr(context, "user_id", None),
schedule_id=getattr(context, "schedule_id", None),
platform=getattr(context, "platform_scope", None)
or PlatformUtils.get_platform_scope(bot),
)
@dataclasses.dataclass
class SessionContext(Generic[AgentDepsT]):
"""
@@ -66,9 +108,7 @@ class SessionContext(Generic[AgentDepsT]):
"""强类型的外部依赖注入对象(如 Bot, Event),供跨工具共享。"""
shared_state: dict[str, Any] = dataclasses.field(default_factory=dict)
"""共享状态字典:全局引用穿透,用于主智能体与嵌套子智能体之间的数据通信。"""
auth_tokens: dict[str, str] = dataclasses.field(default_factory=dict)
"""授权凭证字典:保存用户针对各 Provider 的 OAuth 或 API Token。"""
blackboard: Any | None = None
blackboard: BlackboardManager | None = None
"""结构化黑板管理器,作为共享状态的高级替代方案,提供并发锁和强类型校验。"""
namespace: str = "global"
"""触发事件的插件命名空间"""
@@ -76,7 +116,7 @@ class SessionContext(Generic[AgentDepsT]):
"""用于大模型前缀缓存命中优化的追加写入管理器。"""
@property
def memory(self) -> Any:
def memory(self) -> "AgentSessionFacade":
"""
获取当前会话的持久化记忆访问门面 (AgentSessionFacade)。
提供极简的 history 和 slots 操作 API。
@@ -84,7 +124,6 @@ class SessionContext(Generic[AgentDepsT]):
from zhenxun.services.ai.context.memory.facades import AgentSessionFacade
from zhenxun.services.ai.context.memory.manager import memory_manager
from zhenxun.services.ai.context.memory.types import SessionMetadata
from zhenxun.services.ai.utils.scope import ScopeSelector
user_id = ContextUtils.extract_user_id(self.deps)
group_id = ContextUtils.extract_group_id(self.deps)
@@ -127,9 +166,9 @@ class AgentRunContext(Generic[AgentDepsT]):
"""子智能体委派深度标记,用于防范无限递归嵌套。"""
tool_retries: dict[str, int] = dataclasses.field(default_factory=dict)
"""记录当前轮次内各个工具的累积失败重试次数,用于系统熔断。"""
cancellation_token: Any | None = None
cancellation_token: CancellationToken | None = None
"""全局级联取消令牌,用于跨 Agent 的协程挂起中断。"""
event_bus: Any | None = None
event_bus: EventBus | None = None
"""底层的事件流发射器 (EventBus),由执行引擎在运行时挂载。"""
dynamic_prompts: dict[str, str] = dataclasses.field(default_factory=dict)
@@ -146,6 +185,11 @@ class AgentRunContext(Generic[AgentDepsT]):
"""向当前运行上下文中安全追加业务事件"""
self.messages.append(event)
async def emit(self, event: AgentStreamEvent) -> None:
"""安全地向事件总线发射流式事件(内置判空逻辑,消解业务层的样板代码)"""
if self.event_bus:
await self.event_bus.emit(event)
@dataclasses.dataclass
class ToolCallContext(Generic[AgentDepsT]):
@@ -162,7 +206,7 @@ class ToolCallContext(Generic[AgentDepsT]):
"""本次调用的工具名称。"""
retry_count: int = 0
"""当前工具调用的重试序号 (第几次重试)。"""
current_tool: Any | None = None
current_tool: ToolExecutable | None = None
"""当前工具的可执行实例 (ToolExecutable)。"""
@@ -188,7 +232,7 @@ class RunContext(Generic[AgentDepsT]):
upstream_results: dict[str, Any] = dataclasses.field(default_factory=dict)
"""前置节点产出字典:标准化的数据流载荷契约,键为 Agent Name,值为输出内容"""
capabilities: list[Any] = dataclasses.field(default_factory=list)
capabilities: list[AbstractCapability] = dataclasses.field(default_factory=list)
"""当前上下文绑定的拦截器 (Capabilities) 链,用于生命周期拦截"""
deps: AgentDepsT = dataclasses.field(default=cast(AgentDepsT, None))
@@ -211,6 +255,14 @@ class RunContext(Generic[AgentDepsT]):
)
"""标记 session_id 是否为框架隐式生成的。"""
@classmethod
def from_schedule(
cls, bot: Bot, context: ScheduleContext, **kwargs
) -> "RunContext[ScheduledDeps]":
"""极简构造语法糖:从定时调度任务上下文中直接生成 RunContext"""
deps = ScheduledDeps.from_schedule_context(bot, context)
return cast("RunContext[ScheduledDeps]", cls(deps=cast(Any, deps), **kwargs))
def get_bot(self) -> Bot | None:
"""强类型安全地提取 Bot 实例"""
if not self.deps:
@@ -256,21 +308,15 @@ class RunContext(Generic[AgentDepsT]):
bot = self.get_bot()
event = self.get_event()
if bot and event:
if bot:
meta = ContextUtils.generate_session_meta(
bot, event, scope_builder=Isolation.AGENT_USER()
bot=bot,
event=event,
deps=self.deps,
scope_builder=Isolation.AGENT_USER(),
)
self.session_id = meta.session_id
self._is_auto_session_id = True
else:
uid = self.get_user_id()
gid = self.get_group_id()
if uid and gid:
self.session_id = f"auto_{gid}_{uid}"
self._is_auto_session_id = True
elif uid:
self.session_id = f"auto_private_{uid}"
self._is_auto_session_id = True
ns = "global"
if self.deps:
@@ -286,9 +332,6 @@ class RunContext(Generic[AgentDepsT]):
shared_state=self.shared_state,
namespace=ns,
)
from zhenxun.services.ai.core.engine.append_only import (
AppendOnlyContextManager,
)
self.session.append_only_manager = AppendOnlyContextManager()
@@ -351,8 +394,6 @@ class RunContext(Generic[AgentDepsT]):
new_ctx.run.messages = []
new_ctx.capabilities = []
import uuid
base_sid = self.session_id or "default"
new_ctx.session_id = f"{base_sid}/sub_{member_name}_{uuid.uuid4().hex[:6]}"
new_ctx.session.session_id = new_ctx.session_id
@@ -403,6 +444,7 @@ __all__ = [
"AgentRunContext",
"NoneBotDeps",
"RunContext",
"ScheduledDeps",
"SessionContext",
"ToolCallContext",
"ToolsPrepareFunc",