mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-20 19:50:02 +08:00
♻️ 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:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
0b32d69c9c
commit
922d092650
@@ -1,7 +1,6 @@
|
||||
from zhenxun.services.ai.core.models import CancellationToken
|
||||
|
||||
from .blackboard import BlackboardManager
|
||||
from .capabilities import GLOBAL_CAPABILITIES, register_global_capability
|
||||
from .context import (
|
||||
NoneBotDeps,
|
||||
RunContext,
|
||||
@@ -19,7 +18,6 @@ from .session import session_manager
|
||||
from .ui import UIController
|
||||
|
||||
__all__ = [
|
||||
"GLOBAL_CAPABILITIES",
|
||||
"AgentRunResult",
|
||||
"AgentTask",
|
||||
"BlackboardManager",
|
||||
@@ -33,6 +31,5 @@ __all__ = [
|
||||
"StreamedRunResult",
|
||||
"UIController",
|
||||
"get_current_run_context",
|
||||
"register_global_capability",
|
||||
"session_manager",
|
||||
]
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from zhenxun.services.ai.capabilities import (
|
||||
AbstractCapability,
|
||||
)
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.utils import infer_plugin_namespace
|
||||
|
||||
GLOBAL_CAPABILITIES: dict[str, list[AbstractCapability]] = defaultdict(list)
|
||||
|
||||
from zhenxun.services.ai.capabilities.builtin import (
|
||||
BillingCapability,
|
||||
GlobalCycleLimitCapability,
|
||||
PermissionCapability,
|
||||
ReflexionCapability,
|
||||
StuckDetectionCapability,
|
||||
ToolRetryAndReflectionCapability,
|
||||
)
|
||||
|
||||
for _cap in [
|
||||
GlobalCycleLimitCapability(),
|
||||
StuckDetectionCapability(),
|
||||
PermissionCapability(),
|
||||
BillingCapability(),
|
||||
ToolRetryAndReflectionCapability(),
|
||||
ReflexionCapability(),
|
||||
]:
|
||||
GLOBAL_CAPABILITIES["global"].append(_cap)
|
||||
|
||||
|
||||
def register_global_capability(
|
||||
capability: AbstractCapability, scope: str | None = None
|
||||
) -> None:
|
||||
ns = scope if scope is not None else infer_plugin_namespace()
|
||||
GLOBAL_CAPABILITIES[ns].append(capability)
|
||||
logger.debug(
|
||||
f"已注册全局 Capability: {capability.__class__.__name__} -> Namespace: {ns}"
|
||||
)
|
||||
@@ -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",
|
||||
|
||||
@@ -9,9 +9,9 @@ from nonebot.utils import is_coroutine_callable
|
||||
from nonebot_plugin_session import EventSession, extract_session
|
||||
|
||||
from zhenxun.services.ai.context.memory.facades import AgentSessionFacade
|
||||
from zhenxun.services.ai.run.blackboard import BlackboardManager
|
||||
from zhenxun.utils.utils import infer_plugin_namespace
|
||||
|
||||
from .blackboard import BlackboardManager
|
||||
from .context import ProviderFunc, RunContext, _is_run_context_type
|
||||
from .hitl import HITLController
|
||||
from .ui import UIController
|
||||
@@ -105,6 +105,10 @@ class Inject:
|
||||
"""
|
||||
return _UpstreamResultMarker(step_name)
|
||||
|
||||
CurrentBlackboardState = Annotated[Any, Hidden(), _InjectMarker("blackboard_state")]
|
||||
BlackboardState = CurrentBlackboardState
|
||||
"""自动注入:当前挂载的强类型黑板底层 Pydantic 实例(需配合 Annotated 使用)"""
|
||||
|
||||
UserId = CurrentUserId
|
||||
"""自动注入:触发当前任务的用户 ID"""
|
||||
|
||||
@@ -366,6 +370,19 @@ def _resolve_blackboard(ctx: RunContext):
|
||||
Inject.register_provider("blackboard", _resolve_blackboard, scope="global")
|
||||
|
||||
|
||||
def _resolve_blackboard_state(ctx: RunContext):
|
||||
bb = ctx.session.blackboard
|
||||
if bb is None:
|
||||
raise ValueError(
|
||||
"Inject.BlackboardState 注入失败:"
|
||||
"当前会话上下文中未挂载 BlackboardManager 实例。"
|
||||
)
|
||||
return bb._state
|
||||
|
||||
|
||||
Inject.register_provider("blackboard_state", _resolve_blackboard_state, scope="global")
|
||||
|
||||
|
||||
def _resolve_session(ctx):
|
||||
bot = ctx.get_bot()
|
||||
event = ctx.get_event()
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_waiter import waiter
|
||||
|
||||
from zhenxun.services.ai.core.exceptions import AbortException, ToolFatalError
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.services.ai.utils.logger import log_agent as logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from .context import RunContext
|
||||
|
||||
CANCEL_WORDS = {"取消", "cancel", "0", "退出", "quit"}
|
||||
CONFIRM_WORDS = {"y", "yes", "是", "1", "ok", "确认"}
|
||||
@@ -24,7 +24,7 @@ class HITLController:
|
||||
封装底层的物理环境隔离等待逻辑,供上层工具和中间件发起提问或审批。
|
||||
"""
|
||||
|
||||
def __init__(self, context: "RunContext"):
|
||||
def __init__(self, context: RunContext):
|
||||
self.context = context
|
||||
|
||||
async def wait_event(
|
||||
@@ -42,7 +42,9 @@ class HITLController:
|
||||
|
||||
if not bot or not event:
|
||||
logger.warning("HITLController: 当前环境无 Bot/Event 实例,无法发起交互。")
|
||||
return None
|
||||
raise ToolFatalError(
|
||||
"当前处于自动化后台调度环境,无法发起人工交互。任务已强行中止。"
|
||||
)
|
||||
|
||||
if prompt_msg:
|
||||
await bot.send(event, prompt_msg)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
运行时(Run)相关核心类型定义
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
import json
|
||||
from typing import Any, Generic, cast
|
||||
from typing_extensions import TypeVar
|
||||
@@ -10,12 +12,16 @@ from typing_extensions import TypeVar
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
||||
|
||||
from zhenxun.services.ai.core.messages import AgentMessage, LLMMessage, UsageInfo
|
||||
from zhenxun.services.ai.core.options import BaseOutputDefinition
|
||||
from zhenxun.services.ai.core.protocols.tool import ToolResolvable
|
||||
from zhenxun.services.ai.core.stream_events import (
|
||||
AgentStreamEvent,
|
||||
EventBus,
|
||||
ToolCallStartEvent,
|
||||
ToolStreamChunkEvent,
|
||||
)
|
||||
from zhenxun.services.ai.guardrails import BaseGuardrail, GuardrailSource
|
||||
from zhenxun.services.ai.tools.core.tool import BaseTool
|
||||
from zhenxun.utils.pydantic_compat import model_dump, model_validator
|
||||
|
||||
|
||||
@@ -37,7 +43,7 @@ class ToolSummary(BaseModel):
|
||||
"""工具执行失败次数"""
|
||||
total_latency_ms: float = 0.0
|
||||
"""工具执行总耗时(毫秒)"""
|
||||
by_name: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
||||
by_name: dict[str, dict[str, float | int]] = Field(default_factory=dict)
|
||||
"""按工具名称细分的执行状态统计"""
|
||||
|
||||
|
||||
@@ -126,9 +132,8 @@ class StreamedRunResult(Generic[OutputDataT]):
|
||||
self.is_complete: bool = False
|
||||
self._result: AgentRunResult[OutputDataT] | None = None
|
||||
|
||||
async def stream_events(self) -> AsyncIterator[Any]:
|
||||
async def stream_events(self) -> "AsyncIterator[AgentStreamEvent]":
|
||||
"""获取底层的所有原始事件(包含工具调用过程等)"""
|
||||
from zhenxun.services.ai.run.models import AgentRunEnd, AgentRunError
|
||||
|
||||
async for event in self._event_bus:
|
||||
if isinstance(event, AgentRunEnd):
|
||||
@@ -214,24 +219,6 @@ class StreamedRunResult(Generic[OutputDataT]):
|
||||
return await self.get_run_result()
|
||||
|
||||
|
||||
class TaskResult(BaseModel):
|
||||
"""单个数据契约任务的执行结果"""
|
||||
|
||||
task_id: str
|
||||
"""关联的任务唯一 ID"""
|
||||
|
||||
output: Any
|
||||
"""任务的实际产出(如果是结构化任务,则为解析后的 Pydantic 实例;否则为纯文本)"""
|
||||
|
||||
raw_response: str | None = None
|
||||
"""大模型返回的原始纯文本内容"""
|
||||
|
||||
usage: UsageInfo = Field(default_factory=UsageInfo)
|
||||
"""该任务执行期间的 Token 消耗统计"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
|
||||
class AgentTask(BaseModel):
|
||||
"""标准化数据契约(意图载体 Payload),定义大模型需要做什么及产出什么格式"""
|
||||
|
||||
@@ -247,23 +234,25 @@ class AgentTask(BaseModel):
|
||||
expected_output: str
|
||||
"""预期输出的自然语言描述(指导大模型如何组织最终答案)"""
|
||||
|
||||
response_model: Any | None = None
|
||||
response_model: type[BaseModel] | BaseOutputDefinition | None = None
|
||||
"""强制要求返回的强类型结构 (Pydantic Model) 或
|
||||
OutputDefinition,为空则返回普通文本"""
|
||||
|
||||
tools: list[str | Any] | None = None
|
||||
tools: list[str | Callable | dict[str, Any] | BaseTool | ToolResolvable] | None = (
|
||||
None
|
||||
)
|
||||
"""针对此特定任务动态追加或覆盖的工具列表"""
|
||||
|
||||
guardrails: list[Any] | None = None
|
||||
guardrails: list[GuardrailSource] | None = None
|
||||
"""护栏验证列表。支持传入函数、BaseGuardrail 实例,
|
||||
或直接传入自然语言字符串规则(自动转为 LLM 裁判)"""
|
||||
|
||||
_parsed_guardrails: list[Any] = PrivateAttr(default_factory=list)
|
||||
_parsed_guardrails: list[BaseGuardrail] = PrivateAttr(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _parse_and_set_guardrails(self) -> "AgentTask":
|
||||
def _parse_and_set_guardrails(self) -> AgentTask:
|
||||
from zhenxun.services.ai.guardrails import parse_guardrails
|
||||
|
||||
self._parsed_guardrails = parse_guardrails(self.guardrails)
|
||||
@@ -275,5 +264,4 @@ __all__ = [
|
||||
"AgentTask",
|
||||
"OutputDataT",
|
||||
"StreamedRunResult",
|
||||
"TaskResult",
|
||||
]
|
||||
|
||||
@@ -108,7 +108,7 @@ class AgentSessionManager:
|
||||
count += 1
|
||||
if task and not task.done():
|
||||
task.cancel()
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.services.ai.utils.logger import log_agent as logger
|
||||
|
||||
if count > 0:
|
||||
logger.info(
|
||||
|
||||
@@ -13,9 +13,12 @@ from zhenxun.services.ai.core.stream_events import (
|
||||
ToolStreamChunkEvent,
|
||||
UserCustomEvent,
|
||||
)
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.run.models import AgentRunEnd, AgentRunStart, AgentRunSummary
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.services.ai.utils.logger import log_agent as logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
from .context import RunContext
|
||||
from .models import AgentRunEnd, AgentRunStart, AgentRunSummary
|
||||
|
||||
|
||||
class TelemetrySubscriber:
|
||||
@@ -122,7 +125,7 @@ class DefaultUISubscriber:
|
||||
|
||||
async def _send_to_platform(self, display: Any):
|
||||
"""将通用的显示内容或富文本消息部件渲染并发送至具体的聊天平台"""
|
||||
if not self.bot or not self.event or not display:
|
||||
if not self.bot or not display:
|
||||
return
|
||||
|
||||
if (
|
||||
@@ -144,9 +147,27 @@ class DefaultUISubscriber:
|
||||
display = msg
|
||||
|
||||
if isinstance(display, UniMessage):
|
||||
await display.send(self.event, bot=self.bot, reply_to=self.reply_to)
|
||||
if self.event:
|
||||
await display.send(self.event, bot=self.bot, reply_to=self.reply_to)
|
||||
else:
|
||||
target = PlatformUtils.get_target(
|
||||
user_id=self.context.get_user_id(),
|
||||
group_id=self.context.get_group_id(),
|
||||
)
|
||||
if target:
|
||||
await display.send(target=target, bot=self.bot)
|
||||
else:
|
||||
await self.bot.send(self.event, str(display))
|
||||
if self.event:
|
||||
await self.bot.send(self.event, str(display))
|
||||
else:
|
||||
target = PlatformUtils.get_target(
|
||||
user_id=self.context.get_user_id(),
|
||||
group_id=self.context.get_group_id(),
|
||||
)
|
||||
if target:
|
||||
await MessageUtils.build_message(str(display)).send(
|
||||
target=target, bot=self.bot
|
||||
)
|
||||
|
||||
async def on_tool_stream(self, event: ToolStreamChunkEvent):
|
||||
"""处理工具流式数据块事件,仅在 verbose 开启时发送给平台"""
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
|
||||
from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent, UserCustomEvent
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from .context import RunContext
|
||||
|
||||
|
||||
class UIController:
|
||||
"""前端 UI 富交互流式控制器 (按需生成模式)"""
|
||||
|
||||
def __init__(self, context: "RunContext"):
|
||||
def __init__(self, context: RunContext):
|
||||
self.context = context
|
||||
|
||||
@property
|
||||
@@ -19,38 +22,31 @@ class UIController:
|
||||
"""从上下文中动态获取当前调用的工具名"""
|
||||
return getattr(self.context.call, "tool_name", "UnknownTool")
|
||||
|
||||
@property
|
||||
def _event_bus(self) -> Any | None:
|
||||
"""从运行时上下文中获取底层的事件发射器"""
|
||||
return getattr(self.context.run, "event_bus", None)
|
||||
|
||||
async def send_text(self, text: str, status: str = "running") -> None:
|
||||
"""向前端流式反馈执行进度文本"""
|
||||
if self._event_bus:
|
||||
await self._event_bus.emit(
|
||||
ToolStreamChunkEvent(
|
||||
tool_name=self.tool_name, content=text, metadata={"status": status}
|
||||
)
|
||||
await self.context.run.emit(
|
||||
ToolStreamChunkEvent(
|
||||
tool_name=self.tool_name, content=text, metadata={"status": status}
|
||||
)
|
||||
)
|
||||
|
||||
async def send_image(self, image: bytes | str) -> None:
|
||||
"""向前端发送富文本图片气泡(字节流或 URL)"""
|
||||
if self._event_bus:
|
||||
display_msg = UniMessage()
|
||||
if isinstance(image, bytes):
|
||||
display_msg = display_msg.image(raw=image)
|
||||
else:
|
||||
display_msg = display_msg.image(url=image)
|
||||
await self._event_bus.emit(UserCustomEvent(display=display_msg))
|
||||
display_msg = UniMessage()
|
||||
if isinstance(image, bytes):
|
||||
display_msg = display_msg.image(raw=image)
|
||||
else:
|
||||
display_msg = display_msg.image(url=image)
|
||||
await self.context.run.emit(UserCustomEvent(display=display_msg))
|
||||
|
||||
async def send_display(self, display: Any) -> None:
|
||||
"""向前端发送任意展示载体"""
|
||||
if self._event_bus and display is not None:
|
||||
await self._event_bus.emit(UserCustomEvent(display=display))
|
||||
if display is not None:
|
||||
await self.context.run.emit(UserCustomEvent(display=display))
|
||||
|
||||
@staticmethod
|
||||
async def handle_control_flow_exit_display(
|
||||
e: BaseException, context: "RunContext | None", reply_to: bool = False
|
||||
e: BaseException, context: RunContext | None, reply_to: bool = False
|
||||
) -> None:
|
||||
"""统一处理 ControlFlowExit 异常带来的 UI 反馈逻辑"""
|
||||
from zhenxun.services.ai.core.exceptions import ControlFlowExit
|
||||
@@ -63,19 +59,24 @@ class UIController:
|
||||
return
|
||||
|
||||
try:
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
|
||||
bot = context.get_bot() if context else None
|
||||
event = context.get_event() if context else None
|
||||
|
||||
if bot and event:
|
||||
if isinstance(display_msg, UniMessage):
|
||||
await display_msg.send(event, bot=bot, reply_to=reply_to)
|
||||
else:
|
||||
await bot.send(event, str(display_msg))
|
||||
if bot:
|
||||
msg_to_send = (
|
||||
display_msg
|
||||
if isinstance(display_msg, UniMessage)
|
||||
else MessageUtils.build_message(str(display_msg))
|
||||
)
|
||||
if event:
|
||||
await msg_to_send.send(event, bot=bot, reply_to=reply_to)
|
||||
elif context:
|
||||
target = PlatformUtils.get_target(
|
||||
user_id=context.get_user_id(), group_id=context.get_group_id()
|
||||
)
|
||||
if target:
|
||||
await msg_to_send.send(target=target, bot=bot)
|
||||
else:
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
await MessageUtils.build_message(str(display_msg)).send(
|
||||
reply_to=reply_to
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user