mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-05 11:49:59 +08:00
* ✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 - 【重构】将原 services/llm 重构并迁移至全新的 services/ai 架构,提供向下兼容垫片 - 【新增】引入 Agent、Team、Workflow 三大智能体与工作流编排范式 - 【新增】引入基于 RAG 的长期向量记忆与中期槽位记忆系统 - 【新增】引入基于 Docker 的安全代码执行沙箱环境 - 【新增】支持 MCP 协议,允许动态管理和调用 MCP 服务 - 【新增】引入输入输出安全合规护栏与自愈反思机制 - 【优化】重构并优化多厂商 API 适配器 (Gemini, OpenAI, DeepSeek, GLM 等) - 【优化】优化日志脱敏与 Token 预估机制 - 【移除】移除旧版 llm default 和 llm reset-key 命令,新增 llm mcp 管理命令 * 🔧 chore(deps): 更新项目依赖与配置 - 添加 mcp、jieba 和 aiodocker 依赖到配置文件及 requirements.txt - 在 pyright 配置中设置 reportMissingImports 为 none - 调整 .gitignore 中 resources 目录的忽略规则 * ♻️ refactor(tools): 重构工具终止机制并清理知识库日志输出 - 统一使用 `context.state["__end_run__"]` 替代 `EndRunResult` 控制任务结束 - 移除文件系统和向量知识库检索工具中 `ToolResult` 的 `.with_log` 调用 - 调整指令处理器(Directive)的返回值为 `tool_res.output` - 修复部分类型检查警告并优化联合类型判断语法 * ♻️ refactor(tools): 重构工具副作用指令与控制流熔断机制 - 引入 `DirectivePayload` 及 `ToolResult` 的子类以结构化表达工具副作用 - 移除通过 `context.state` 传递魔术变量的隐式控制流设计 - 重构 `DirectiveManager` 处理器接口,直接在处理器中修改 `AgentState` 并构建 `AgentRunResult` - 在 `StandardAgentExecutor` 中统一通过 `directive_manager` 调度工具返回的副作用指令 - 补全 `MessageBuilder` 中部分核心方法的文档注释 * 🐛 fix(sandbox): 修复 Docker 沙箱容器状态检测与会话清理逻辑 -【修复】修正 `is_alive` 中直接读取私有属性的问题,改用 `show()` 返回值 -【修复】解决 `execute_code` 中缓存的执行器与当前会话不一致的问题 -【优化】在清理工作区前增加容器存活检测,避免向已死容器发送请求 -【优化】创建容器时增加运行状态校验,若已停止则自动从缓存中移除并重建 -【优化】优化容器销毁和清理逻辑,静默处理容器不存在 (404) 的异常 * 📝 docs(core): 补充核心模块初始化方法的文档注释 * 🚨 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>
435 lines
16 KiB
Python
435 lines
16 KiB
Python
from collections.abc import Callable
|
|
import copy
|
|
import inspect
|
|
from typing import Any, cast
|
|
|
|
from nonebot.utils import is_coroutine_callable
|
|
|
|
from zhenxun.services.ai.capabilities import CombinedCapability
|
|
from zhenxun.services.ai.context.memory.models import MemoryConfig
|
|
from zhenxun.services.ai.core.messages import LLMMessage
|
|
from zhenxun.services.ai.core.options import GenerationConfig
|
|
from zhenxun.services.ai.core.templates import PromptTemplate
|
|
from zhenxun.services.ai.flow.agent.models import Persona
|
|
from zhenxun.services.ai.run import RunContext
|
|
from zhenxun.services.ai.run.di import DependencyInjector
|
|
from zhenxun.services.ai.tools.engine.registry import (
|
|
ToolCollection,
|
|
tool_provider_manager,
|
|
)
|
|
from zhenxun.services.ai.tools.models import GlobalToolFilter, ResolvedToolPayload
|
|
from zhenxun.utils.pydantic_compat import model_copy
|
|
|
|
|
|
class AgentProfileResolver:
|
|
"""Agent 配置解析器:负责提取与合并 Agent 的运行时 Profile"""
|
|
|
|
@staticmethod
|
|
def resolve_memory(
|
|
agent_memory_config: MemoryConfig, override_memory: Any | None
|
|
) -> MemoryConfig:
|
|
from zhenxun.services.ai.context.memory.builder import MemoryBuilder
|
|
|
|
if override_memory is not None:
|
|
return MemoryBuilder.resolve(override_memory)
|
|
return model_copy(agent_memory_config, deep=True)
|
|
|
|
@staticmethod
|
|
def resolve_generation_config(
|
|
base_config: GenerationConfig,
|
|
cap_config: GenerationConfig | None,
|
|
profile_config: GenerationConfig | None,
|
|
) -> GenerationConfig:
|
|
final_gen_config = model_copy(base_config, deep=True)
|
|
if cap_config:
|
|
final_gen_config = final_gen_config.merge_with(cap_config)
|
|
if profile_config:
|
|
final_gen_config = final_gen_config.merge_with(profile_config)
|
|
return final_gen_config
|
|
|
|
|
|
class CapabilityBuilder:
|
|
"""拦截器能力组装器:负责合并 Agent, Task, Profile 和全局的中间件"""
|
|
|
|
@staticmethod
|
|
async def build_for_run(
|
|
agent_name: str,
|
|
namespace: str,
|
|
output_type: Any | None,
|
|
raw_schema: dict | None,
|
|
agent_guardrails: list,
|
|
task_guardrails: list,
|
|
task_obj: Any | None,
|
|
agent_capabilities: list,
|
|
profile_capabilities: list | None,
|
|
context: RunContext,
|
|
) -> CombinedCapability:
|
|
from zhenxun.services.ai.capabilities import (
|
|
AbstractCapability,
|
|
DynamicCapability,
|
|
)
|
|
from zhenxun.services.ai.flow.agent.capabilities import (
|
|
OutputValidationCapability,
|
|
TaskTrackingCapability,
|
|
)
|
|
|
|
dynamic_caps = []
|
|
combined_guardrails = agent_guardrails + task_guardrails
|
|
|
|
if output_type is not None and output_type is not str:
|
|
dynamic_caps.append(
|
|
OutputValidationCapability(output_type, combined_guardrails)
|
|
)
|
|
elif raw_schema is not None:
|
|
dynamic_caps.append(
|
|
OutputValidationCapability(
|
|
None, combined_guardrails, raw_schema=raw_schema
|
|
)
|
|
)
|
|
elif combined_guardrails:
|
|
dynamic_caps.append(OutputValidationCapability(None, combined_guardrails))
|
|
|
|
if task_obj:
|
|
dynamic_caps.append(TaskTrackingCapability(task_obj, agent_name))
|
|
|
|
run_level_caps = []
|
|
if profile_capabilities:
|
|
for cap in profile_capabilities:
|
|
if isinstance(cap, AbstractCapability):
|
|
run_level_caps.append(cap)
|
|
elif callable(cap):
|
|
run_level_caps.append(DynamicCapability(cap))
|
|
|
|
from zhenxun.services.ai.run import (
|
|
GLOBAL_CAPABILITIES,
|
|
)
|
|
|
|
base_caps = GLOBAL_CAPABILITIES.get("global", []).copy()
|
|
if namespace != "global" and namespace in GLOBAL_CAPABILITIES:
|
|
base_caps.extend(GLOBAL_CAPABILITIES[namespace])
|
|
|
|
combined_cap = CombinedCapability(
|
|
base_caps
|
|
+ getattr(context, "capabilities", [])
|
|
+ agent_capabilities
|
|
+ run_level_caps
|
|
+ dynamic_caps
|
|
)
|
|
return cast(CombinedCapability, await combined_cap.for_run(context))
|
|
|
|
|
|
class ContextBuilder:
|
|
"""系统提示词与上下文记忆构建器"""
|
|
|
|
@staticmethod
|
|
async def build_prompts(
|
|
instruction: str | PromptTemplate,
|
|
system_prompts: list[Any],
|
|
run_context: RunContext,
|
|
run_scoped_cap: CombinedCapability,
|
|
persona: Persona | None = None,
|
|
) -> tuple[str, list[Any]]:
|
|
"""解析提示词,返回 (静态系统提示词文本, 动态独立消息列表) 元组"""
|
|
|
|
static_instructions = []
|
|
dynamic_messages = []
|
|
|
|
for sp_func in system_prompts:
|
|
sig = inspect.signature(sp_func)
|
|
if len(sig.parameters) > 0:
|
|
injected_kwargs = await DependencyInjector.resolve_all(
|
|
sig=sig,
|
|
call_kwargs={},
|
|
context=run_context,
|
|
)
|
|
res = (
|
|
(await sp_func(**injected_kwargs))
|
|
if is_coroutine_callable(sp_func)
|
|
else sp_func(**injected_kwargs)
|
|
)
|
|
else:
|
|
res = (await sp_func()) if is_coroutine_callable(sp_func) else sp_func()
|
|
if res:
|
|
if isinstance(res, LLMMessage):
|
|
dynamic_messages.append(res)
|
|
elif isinstance(res, list) and all(
|
|
isinstance(m, LLMMessage) for m in res
|
|
):
|
|
dynamic_messages.extend(res)
|
|
else:
|
|
if isinstance(res, list):
|
|
for item in res:
|
|
if item:
|
|
dynamic_messages.append(LLMMessage.system(str(item)))
|
|
else:
|
|
dynamic_messages.append(LLMMessage.system(str(res)))
|
|
|
|
if persona:
|
|
persona_parts = [
|
|
f"## 扮演角色 (Role)\n{persona.role}",
|
|
f"## 核心目标 (Goal)\n{persona.goal}",
|
|
]
|
|
if persona.backstory:
|
|
persona_parts.append(f"## 角色背景 (Backstory)\n{persona.backstory}")
|
|
static_instructions.append("\n\n".join(persona_parts))
|
|
|
|
if instruction:
|
|
static_instructions.append("## 本次任务指令 (Task)")
|
|
|
|
if instruction:
|
|
if isinstance(instruction, PromptTemplate):
|
|
static_instructions.append(instruction.format_with_context(run_context))
|
|
else:
|
|
static_instructions.append(str(instruction))
|
|
|
|
caps = (
|
|
run_scoped_cap.capabilities
|
|
if run_scoped_cap
|
|
else getattr(run_context, "capabilities", [])
|
|
)
|
|
for cap in caps:
|
|
cap_prompts = await cap.get_system_prompts(run_context)
|
|
for prompt_text in cap_prompts:
|
|
if prompt_text and prompt_text.strip():
|
|
dynamic_messages.append(LLMMessage.system(prompt_text))
|
|
|
|
static_text = "\n\n".join(static_instructions)
|
|
|
|
render_context = {
|
|
"deps": run_context.deps,
|
|
"bot": getattr(run_context.deps, "bot", None),
|
|
"event": getattr(run_context.deps, "event", None),
|
|
"matcher": getattr(run_context.deps, "matcher", None),
|
|
}
|
|
if run_context.state:
|
|
render_context.update(run_context.state)
|
|
|
|
rendered_dynamic_messages = []
|
|
from zhenxun.services.ai.core.messages import TextPart
|
|
|
|
for msg in dynamic_messages:
|
|
if msg.role == "system":
|
|
new_content = []
|
|
changed = False
|
|
for part in msg.content:
|
|
if isinstance(part, TextPart) and part.text:
|
|
try:
|
|
rendered_text = PromptTemplate(part.text).render(
|
|
**render_context
|
|
)
|
|
new_content.append(TextPart(text=rendered_text))
|
|
if rendered_text != part.text:
|
|
changed = True
|
|
except Exception:
|
|
new_content.append(part)
|
|
else:
|
|
new_content.append(part)
|
|
if changed:
|
|
new_msg = msg.model_copy(deep=True)
|
|
new_msg.content = new_content
|
|
rendered_dynamic_messages.append(new_msg)
|
|
else:
|
|
rendered_dynamic_messages.append(msg)
|
|
else:
|
|
rendered_dynamic_messages.append(msg)
|
|
|
|
return (
|
|
PromptTemplate(static_text).render(**render_context),
|
|
rendered_dynamic_messages,
|
|
)
|
|
|
|
|
|
class ToolBuilder:
|
|
"""系统工具集合解析与构建器"""
|
|
|
|
@staticmethod
|
|
async def resolve_tools(
|
|
tool_definitions: list[Any],
|
|
toolset_funcs: list[Any],
|
|
system_tools: list[Any],
|
|
namespace: str,
|
|
tool_filter: GlobalToolFilter | None,
|
|
run_context: RunContext,
|
|
run_scoped_cap: CombinedCapability,
|
|
) -> ResolvedToolPayload:
|
|
"""解析、合并并过滤工具集"""
|
|
defs_to_resolve = list(tool_definitions)
|
|
|
|
for ts_func in toolset_funcs:
|
|
sig = inspect.signature(ts_func)
|
|
injected_kwargs = {}
|
|
if len(sig.parameters) > 0:
|
|
injected_kwargs = await DependencyInjector.resolve_all(
|
|
sig=sig,
|
|
call_kwargs={},
|
|
context=run_context,
|
|
)
|
|
|
|
res = (
|
|
(await ts_func(**injected_kwargs))
|
|
if is_coroutine_callable(ts_func)
|
|
else ts_func(**injected_kwargs)
|
|
)
|
|
|
|
if res is not None:
|
|
if isinstance(res, list):
|
|
defs_to_resolve.extend(res)
|
|
else:
|
|
defs_to_resolve.append(res)
|
|
|
|
if system_tools:
|
|
for st in system_tools:
|
|
if st not in defs_to_resolve:
|
|
defs_to_resolve.append(st)
|
|
|
|
caps = (
|
|
run_scoped_cap.capabilities
|
|
if run_scoped_cap
|
|
else getattr(run_context, "capabilities", [])
|
|
)
|
|
for cap in caps:
|
|
cap_tools = await cap.get_tools(run_context)
|
|
defs_to_resolve.extend(cap_tools)
|
|
|
|
payload = await tool_provider_manager.resolve_tools(
|
|
defs_to_resolve, namespace, context=run_context
|
|
)
|
|
|
|
return payload
|
|
|
|
@staticmethod
|
|
async def prepare_effective_tools(
|
|
effective_tools: list[Any],
|
|
context: RunContext,
|
|
tool_filters: list[Callable],
|
|
run_scoped_cap: CombinedCapability,
|
|
) -> ToolCollection:
|
|
"""处理生命周期:在工具发往执行器前,进行最终的 Schema 拦截和清洗"""
|
|
current_tool_defs = []
|
|
for t_exec in effective_tools:
|
|
if hasattr(t_exec, "get_definition"):
|
|
t_def = await t_exec.get_definition(context)
|
|
if t_def:
|
|
current_tool_defs.append(t_def)
|
|
|
|
if tool_filters:
|
|
for filter_func in tool_filters:
|
|
sig = inspect.signature(filter_func)
|
|
call_kwargs = {"tool_defs": current_tool_defs}
|
|
resolved_kwargs = await DependencyInjector.resolve_all(
|
|
sig, call_kwargs, context
|
|
)
|
|
filtered_kwargs = {
|
|
k: v for k, v in resolved_kwargs.items() if k in sig.parameters
|
|
}
|
|
_res = (
|
|
await filter_func(**filtered_kwargs)
|
|
if is_coroutine_callable(filter_func)
|
|
else filter_func(**filtered_kwargs)
|
|
)
|
|
if _res is not None:
|
|
current_tool_defs = list(_res)
|
|
|
|
_cap_res = await run_scoped_cap.prepare_tools(context, current_tool_defs)
|
|
if _cap_res is not None:
|
|
current_tool_defs = list(_cap_res)
|
|
|
|
final_defs_map = {d.name.lower(): d for d in current_tool_defs if d}
|
|
final_effective_tools = ToolCollection()
|
|
for t_exec in effective_tools:
|
|
t_name = getattr(t_exec, "name", "unknown")
|
|
if t_name.lower() in final_defs_map:
|
|
cloned_tool = copy.copy(t_exec)
|
|
cloned_tool._dynamic_def = final_defs_map[t_name.lower()]
|
|
final_effective_tools.append(cloned_tool)
|
|
return final_effective_tools
|
|
|
|
|
|
class SessionBuilder:
|
|
"""会话与记忆域构建器:负责隔离前缀计算和读写门面装配"""
|
|
|
|
@staticmethod
|
|
def build_session_and_memory(
|
|
context: RunContext,
|
|
namespace: str,
|
|
agent_name: str,
|
|
effective_memory: MemoryConfig,
|
|
) -> tuple[Any, Any, Any]:
|
|
from zhenxun.services.ai.context.memory.engine import MemoryReader, MemoryWriter
|
|
from zhenxun.services.ai.context.memory.types import SessionMetadata
|
|
from zhenxun.services.ai.utils.scope import ScopeSelector
|
|
|
|
bot_id = None
|
|
bot_inst = context.get_bot()
|
|
if bot_inst and hasattr(bot_inst, "self_id"):
|
|
bot_id = str(bot_inst.self_id)
|
|
|
|
selector = ScopeSelector(
|
|
user_id=context.get_user_id(),
|
|
group_id=context.get_group_id(),
|
|
platform=context.get_platform(),
|
|
bot_id=bot_id,
|
|
namespace=namespace,
|
|
agent_name=agent_name,
|
|
)
|
|
|
|
all_scopes = {"/"}
|
|
scope_name_mapping = {}
|
|
|
|
if effective_memory.short_term and effective_memory.short_term.isolation:
|
|
sel = effective_memory.short_term.isolation.resolve(
|
|
deps=context.deps,
|
|
prefix="",
|
|
default_namespace=namespace,
|
|
default_agent=agent_name,
|
|
)
|
|
all_scopes.add(sel.scope_prefix)
|
|
|
|
for config_part in [effective_memory.slots, effective_memory.long_term]:
|
|
if config_part and hasattr(config_part, "scopes") and config_part.scopes:
|
|
for name, builder in config_part.scopes.items():
|
|
sel = builder.resolve(
|
|
deps=context.deps,
|
|
prefix="",
|
|
default_namespace=namespace,
|
|
default_agent=agent_name,
|
|
)
|
|
all_scopes.add(sel.scope_prefix)
|
|
scope_name_mapping[sel.scope_prefix] = name
|
|
|
|
parts = selector.get_scope_parts()
|
|
for i in range(len(parts)):
|
|
all_scopes.add("/" + "/".join(parts[: i + 1]))
|
|
|
|
accessible_scopes = sorted(all_scopes, key=lambda x: len(x.split("/")))
|
|
|
|
short_term_builder = (
|
|
effective_memory.short_term.isolation
|
|
if effective_memory.short_term
|
|
else effective_memory.base_isolation
|
|
)
|
|
short_term_selector = short_term_builder.resolve(
|
|
deps=context.deps,
|
|
prefix="",
|
|
default_namespace=namespace,
|
|
default_agent=agent_name,
|
|
)
|
|
|
|
session_metadata = SessionMetadata(
|
|
session_id=short_term_selector.scope_prefix,
|
|
selector=selector,
|
|
scope_prefix=selector.scope_prefix,
|
|
accessible_scopes=accessible_scopes,
|
|
scope_name_mapping=scope_name_mapping,
|
|
)
|
|
reader = MemoryReader(
|
|
session_meta=session_metadata, memory_config=effective_memory
|
|
)
|
|
writer = MemoryWriter(
|
|
session_meta=session_metadata,
|
|
memory_config=effective_memory,
|
|
context=context,
|
|
)
|
|
|
|
return session_metadata, reader, writer
|