mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-28 16:20:56 +08:00
* ♻️ refactor(core): 重构 AI 编排框架与记忆及 RAG 子系统 - 【重构】重构 `BaseRunnable` 并引入统一的 `RunIntent` 意图载体,规范 Agent、Team 和 Workflow 的执行流 - 【解耦】将中期记忆槽和长期向量记忆从 `MemoryConfig` 中解耦,转为独立的能力组件与工具箱进行管理 - 【记忆】移除 `MemoryReader` 和 `MemoryWriter`,统一封装为 `SessionMemoryContext` 会话记忆门面 - 【RAG】重构检索器与存储后端接口,统一采用 `QueryRequest` 进行多维度联合检索,并引入 `InMemoryScorer` 提升打分性能 - 【事件】优化 `EventBus` 异步事件分发机制,引入队列机制确保事件按序处理,避免并发竞态问题 - 【依赖注入】移除 `memory` 注入项,优化 `DependencyInjector` 的签名解析缓存以提升性能 * 🚨 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>
414 lines
14 KiB
Python
414 lines
14 KiB
Python
from collections import defaultdict
|
||
from collections.abc import Awaitable, Callable
|
||
from functools import lru_cache
|
||
import inspect
|
||
from typing import Annotated, Any, ClassVar, cast
|
||
|
||
|
||
@lru_cache(maxsize=2048)
|
||
def _get_signature_cached(func: Callable) -> inspect.Signature:
|
||
return inspect.signature(func)
|
||
|
||
|
||
def get_signature(func: Callable) -> inspect.Signature:
|
||
try:
|
||
return _get_signature_cached(func)
|
||
except TypeError:
|
||
return inspect.signature(func)
|
||
|
||
|
||
from nonebot.adapters import Bot, Event
|
||
from nonebot.matcher import Matcher
|
||
from nonebot.utils import is_coroutine_callable
|
||
from nonebot_plugin_session import EventSession, extract_session
|
||
|
||
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
|
||
|
||
|
||
class Hidden:
|
||
"""
|
||
标记一个参数为隐藏参数。
|
||
在生成大模型工具 Schema 时,带有该标记的 Annotated 参数将被剔除。
|
||
"""
|
||
|
||
pass
|
||
|
||
|
||
class _InjectMarker:
|
||
"""内部魔术标记,用于识别 DI 类型糖"""
|
||
|
||
def __init__(self, key: str):
|
||
self.key = key
|
||
|
||
|
||
class _UpstreamResultMarker:
|
||
"""内部魔术标记,用于精准捕获上游特定节点的产出"""
|
||
|
||
def __init__(self, step_name: str):
|
||
self.step_name = step_name
|
||
|
||
|
||
CurrentUserId = Annotated[str | None, Hidden(), _InjectMarker("user_id")]
|
||
CurrentGroupId = Annotated[str | None, Hidden(), _InjectMarker("group_id")]
|
||
CurrentPlatform = Annotated[str, Hidden(), _InjectMarker("platform")]
|
||
CurrentBot = Annotated[Bot | None, Hidden(), _InjectMarker("bot")]
|
||
CurrentEvent = Annotated[Event | None, Hidden(), _InjectMarker("event")]
|
||
CurrentMatcher = Annotated[Matcher | None, Hidden(), _InjectMarker("matcher")]
|
||
CurrentSession = Annotated[EventSession | None, Hidden(), _InjectMarker("session")]
|
||
CurrentUI = Annotated[UIController, Hidden(), _InjectMarker("ui")]
|
||
CurrentHITL = Annotated[HITLController, Hidden(), _InjectMarker("hitl")]
|
||
CurrentModelName = Annotated[str | None, Hidden(), _InjectMarker("model_name")]
|
||
CurrentToolRetries = Annotated[int, Hidden(), _InjectMarker("tool_retries")]
|
||
CurrentState = Annotated[dict[str, Any], Hidden(), _InjectMarker("state")]
|
||
CurrentSharedState = Annotated[dict[str, Any], Hidden(), _InjectMarker("shared_state")]
|
||
CurrentOriginalInput = Annotated[str, Hidden(), _InjectMarker("original_input")]
|
||
UpstreamResults = Annotated[dict[str, Any], Hidden(), _InjectMarker("upstream_results")]
|
||
CurrentEventPayload = Annotated[Any, Hidden(), _InjectMarker("stream_event")]
|
||
CurrentBlackboard = Annotated[
|
||
BlackboardManager | None, Hidden(), _InjectMarker("blackboard")
|
||
]
|
||
|
||
CurrentSandbox = Annotated[Any, Hidden(), _InjectMarker("sandbox")]
|
||
|
||
|
||
class Inject:
|
||
"""
|
||
[命名空间] 大模型工具依赖注入类型糖。
|
||
用于在工具参数中快捷获取群聊/用户的上下文,或注册自定义的依赖提供者。
|
||
"""
|
||
|
||
_providers: ClassVar[dict[str, dict[str, ProviderFunc]]] = defaultdict(dict)
|
||
|
||
@classmethod
|
||
def register_provider(
|
||
cls, key: str, provider: ProviderFunc, scope: str | None = None
|
||
) -> None:
|
||
"""
|
||
(底层) 注册一个自定义的依赖提供者。
|
||
"""
|
||
ns = scope if scope is not None else infer_plugin_namespace()
|
||
cls._providers[key][ns] = provider
|
||
|
||
@classmethod
|
||
def provider(cls, key: str, scope: str | None = None):
|
||
def decorator(func: ProviderFunc) -> ProviderFunc:
|
||
cls.register_provider(key, func, scope)
|
||
return func
|
||
|
||
return decorator
|
||
|
||
@classmethod
|
||
def bind(cls, key: str, type_hint: Any = Any) -> Any:
|
||
"""
|
||
动态生成一个用于类型注解的 Inject 标记。
|
||
示例:DbSession = Inject.bind("db_session", AsyncSession)
|
||
"""
|
||
return Annotated[type_hint, Hidden(), _InjectMarker(key)]
|
||
|
||
@classmethod
|
||
def UpstreamResult(cls, step_name: str) -> Any:
|
||
"""
|
||
自动注入:精准获取工作流中指定前置节点的产出。可作为参数的默认值使用以通过静态检查。
|
||
示例: def my_step(data: str = Inject.UpstreamResult("AgentA")):
|
||
"""
|
||
return _UpstreamResultMarker(step_name)
|
||
|
||
CurrentBlackboardState = Annotated[Any, Hidden(), _InjectMarker("blackboard_state")]
|
||
BlackboardState = CurrentBlackboardState
|
||
"""自动注入:当前挂载的强类型黑板底层 Pydantic 实例(需配合 Annotated 使用)"""
|
||
|
||
UserId = CurrentUserId
|
||
"""自动注入:触发当前任务的用户 ID"""
|
||
|
||
GroupId = CurrentGroupId
|
||
"""自动注入:触发当前任务的群聊 ID(私聊时为 None)"""
|
||
|
||
Platform = CurrentPlatform
|
||
"""自动注入:当前对接的平台名称(如 qq)"""
|
||
|
||
Bot = CurrentBot
|
||
"""自动注入:当前的 NoneBot Bot 实例"""
|
||
|
||
Event = CurrentEvent
|
||
"""自动注入:当前的 NoneBot Event 实例"""
|
||
|
||
Matcher = CurrentMatcher
|
||
"""自动注入:当前的 NoneBot Matcher 实例"""
|
||
|
||
Session = CurrentSession
|
||
"""自动注入:当前的 nonebot_plugin_session 会话实例"""
|
||
|
||
UI = CurrentUI
|
||
"""自动注入:前端 UI 控制器实例"""
|
||
|
||
HITL = CurrentHITL
|
||
"""自动注入:人机协同控制器实例 (提供底层 waiter 交互封装)"""
|
||
|
||
ModelName = CurrentModelName
|
||
"""自动注入:当前大模型正在执行的底层模型名称"""
|
||
|
||
ToolRetries = CurrentToolRetries
|
||
"""自动注入:当前工具正在经历的重试次数 (Int)"""
|
||
|
||
State = CurrentState
|
||
"""自动注入:当前 Agent 轮次隔离的业务状态字典"""
|
||
|
||
OriginalInput = CurrentOriginalInput
|
||
"""自动注入:当前工作流最初始的用户输入文本"""
|
||
|
||
SharedState = CurrentSharedState
|
||
"""自动注入:当前 Team 或全局穿透的共享黑板状态字典"""
|
||
|
||
UpstreamResults = UpstreamResults
|
||
"""自动注入:当前工作流中所有上游节点的产出字典 (Key: Agent Name)"""
|
||
|
||
EventPayload = CurrentEventPayload
|
||
"""自动注入:当前生命周期钩子流转中的 EventBus 载荷实体"""
|
||
|
||
Blackboard = CurrentBlackboard
|
||
"""自动注入:当前工作流/团队挂载的强类型黑板 (BlackboardManager) 实例"""
|
||
|
||
Sandbox = CurrentSandbox
|
||
"""自动注入:当前沙箱环境管理器实例"""
|
||
|
||
|
||
class BaseParamResolver:
|
||
"""可插拔参数解析器基类协议"""
|
||
|
||
def match(
|
||
self,
|
||
name: str,
|
||
param: inspect.Parameter,
|
||
context: RunContext,
|
||
) -> bool:
|
||
return False
|
||
|
||
def static_match(self, param: inspect.Parameter) -> bool:
|
||
return False
|
||
|
||
async def resolve(
|
||
self,
|
||
name: str,
|
||
param: inspect.Parameter,
|
||
context: RunContext,
|
||
) -> Any:
|
||
raise NotImplementedError
|
||
|
||
|
||
class RunContextResolver(BaseParamResolver):
|
||
def match(self, name, param, context) -> bool:
|
||
return _is_run_context_type(param.annotation)
|
||
|
||
def static_match(self, param: inspect.Parameter) -> bool:
|
||
return _is_run_context_type(param.annotation)
|
||
|
||
async def resolve(self, name, param, context) -> Any:
|
||
return context
|
||
|
||
|
||
class TypeSugarResolver(BaseParamResolver):
|
||
def _get_marker(self, param: inspect.Parameter) -> Any | None:
|
||
anno = param.annotation
|
||
if hasattr(anno, "__metadata__"):
|
||
for arg in anno.__metadata__:
|
||
if isinstance(arg, _InjectMarker | _UpstreamResultMarker):
|
||
return arg
|
||
default_val = param.default
|
||
if isinstance(default_val, _InjectMarker | _UpstreamResultMarker):
|
||
return default_val
|
||
if hasattr(default_val, "__metadata__"):
|
||
for arg in default_val.__metadata__:
|
||
if isinstance(arg, _InjectMarker | _UpstreamResultMarker):
|
||
return arg
|
||
return None
|
||
|
||
def match(self, name, param, context) -> bool:
|
||
return self._get_marker(param) is not None
|
||
|
||
def static_match(self, param: inspect.Parameter) -> bool:
|
||
return self._get_marker(param) is not None
|
||
|
||
async def resolve(self, name, param, context) -> Any:
|
||
marker = self._get_marker(param)
|
||
if not marker:
|
||
raise ValueError(f"参数 {name} 缺失 Inject 标记")
|
||
if isinstance(marker, _UpstreamResultMarker):
|
||
return context.upstream_results.get(marker.step_name)
|
||
|
||
marker_key = marker.key
|
||
|
||
if marker_key in context.di_cache:
|
||
return context.di_cache[marker_key]
|
||
|
||
provider_dict = Inject._providers.get(marker_key, {})
|
||
ns = getattr(context.session, "namespace", "global")
|
||
provider = provider_dict.get(ns) or provider_dict.get("global")
|
||
|
||
if not provider:
|
||
raise ValueError(f"未知的类型糖标记或未注册的 Provider: {marker_key}")
|
||
|
||
if is_coroutine_callable(provider):
|
||
result = await provider(context)
|
||
else:
|
||
result = provider(context)
|
||
|
||
context.di_cache[marker_key] = result
|
||
return result
|
||
|
||
|
||
class DependencyInjector:
|
||
"""可插拔依赖注入管线 (Resolver Pipeline)"""
|
||
|
||
_resolvers: ClassVar[list[BaseParamResolver]] = []
|
||
|
||
@classmethod
|
||
def register(cls, resolver: BaseParamResolver) -> None:
|
||
cls._resolvers.append(resolver)
|
||
|
||
@classmethod
|
||
def can_resolve_statically(cls, param: inspect.Parameter) -> bool:
|
||
for resolver in cls._resolvers:
|
||
if resolver.static_match(param):
|
||
return True
|
||
return False
|
||
|
||
@classmethod
|
||
async def resolve_all(
|
||
cls,
|
||
sig: inspect.Signature,
|
||
call_kwargs: dict[str, Any],
|
||
context: RunContext,
|
||
) -> dict[str, Any]:
|
||
resolved_kwargs = dict(call_kwargs)
|
||
for k, v in call_kwargs.items():
|
||
context.di_cache[k] = v
|
||
for name, param in sig.parameters.items():
|
||
if name in ("self", "cls") or name in resolved_kwargs:
|
||
continue
|
||
|
||
if param.kind in (
|
||
inspect.Parameter.VAR_POSITIONAL,
|
||
inspect.Parameter.VAR_KEYWORD,
|
||
):
|
||
continue
|
||
|
||
resolved = False
|
||
for resolver in cls._resolvers:
|
||
if resolver.match(name, param, context):
|
||
val = await resolver.resolve(name, param, context)
|
||
resolved_kwargs[name] = val
|
||
resolved = True
|
||
break
|
||
|
||
if not resolved:
|
||
if param.default is not inspect.Parameter.empty:
|
||
continue
|
||
|
||
raise ValueError(
|
||
f"参数 '{name}' 未被大模型提供且缺少显式的依赖注入标记"
|
||
"(如 Inject.* 或 RunContext)。"
|
||
)
|
||
|
||
return resolved_kwargs
|
||
|
||
@classmethod
|
||
async def invoke(
|
||
cls,
|
||
func: Callable[..., Any],
|
||
call_kwargs: dict[str, Any],
|
||
context: RunContext,
|
||
) -> Any:
|
||
"""统一执行带有依赖注入的函数 (支持同步/异步)"""
|
||
sig = get_signature(func)
|
||
resolved_kwargs = await cls.resolve_all(sig, call_kwargs, context)
|
||
filtered_kwargs = {
|
||
k: v for k, v in resolved_kwargs.items() if k in sig.parameters
|
||
}
|
||
|
||
if is_coroutine_callable(func):
|
||
return await cast(Callable[..., Awaitable[Any]], func)(**filtered_kwargs)
|
||
return cast(Callable[..., Any], func)(**filtered_kwargs)
|
||
|
||
|
||
DependencyInjector.register(RunContextResolver())
|
||
DependencyInjector.register(TypeSugarResolver())
|
||
|
||
Inject.register_provider("user_id", lambda ctx: ctx.get_user_id(), scope="global")
|
||
Inject.register_provider("group_id", lambda ctx: ctx.get_group_id(), scope="global")
|
||
Inject.register_provider("platform", lambda ctx: ctx.get_platform(), scope="global")
|
||
Inject.register_provider("bot", lambda ctx: ctx.get_bot(), scope="global")
|
||
Inject.register_provider("event", lambda ctx: ctx.get_event(), scope="global")
|
||
Inject.register_provider("matcher", lambda ctx: ctx.get_matcher(), scope="global")
|
||
|
||
Inject.register_provider("hitl", lambda ctx: HITLController(ctx), scope="global")
|
||
Inject.register_provider(
|
||
"model_name", lambda ctx: ctx.run.current_model, scope="global"
|
||
)
|
||
|
||
Inject.register_provider(
|
||
"tool_retries", lambda ctx: ctx.call.retry_count, scope="global"
|
||
)
|
||
|
||
Inject.register_provider(
|
||
"original_input", lambda ctx: ctx.run.user_input, scope="global"
|
||
)
|
||
Inject.register_provider("state", lambda ctx: ctx.state, scope="global")
|
||
Inject.register_provider(
|
||
"upstream_results", lambda ctx: ctx.upstream_results, scope="global"
|
||
)
|
||
Inject.register_provider(
|
||
"shared_state", lambda ctx: ctx.session.shared_state, scope="global"
|
||
)
|
||
|
||
|
||
def _resolve_blackboard(ctx: RunContext):
|
||
bb = ctx.session.blackboard
|
||
if bb is None:
|
||
raise ValueError(
|
||
"Inject.Blackboard 注入失败:"
|
||
"当前会话上下文中未挂载 BlackboardManager 实例。"
|
||
)
|
||
return bb
|
||
|
||
|
||
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()
|
||
return extract_session(bot, event) if bot and event else None
|
||
|
||
|
||
Inject.register_provider("session", _resolve_session, scope="global")
|
||
Inject.register_provider("ui", lambda ctx: UIController(ctx), scope="global")
|
||
|
||
from zhenxun.services.ai.sandbox.manager import sandbox_manager
|
||
|
||
Inject.register_provider("sandbox", lambda ctx: sandbox_manager, scope="global")
|
||
|
||
|
||
__all__ = [
|
||
"DependencyInjector",
|
||
"Hidden",
|
||
"Inject",
|
||
]
|