Files
zhenxun_bot/zhenxun/services/ai/run/di.py
T
52f7dbdedf ♻️ refactor(core): 重构 AI 编排框架与记忆及 RAG 子系统 (#2149)
* ♻️ 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>
2026-07-14 16:48:33 +08:00

414 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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",
]