mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-03 19:00:00 +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>
330 lines
12 KiB
Python
330 lines
12 KiB
Python
"""
|
|
工具系统域类型定义
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import TYPE_CHECKING, Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
from zhenxun.services.ai.core.messages import ToolCallPart, UsageInfo
|
|
from zhenxun.services.ai.run.context import RunContext
|
|
from zhenxun.utils.pydantic_compat import model_dump, model_validate
|
|
|
|
if TYPE_CHECKING:
|
|
from zhenxun.services.ai.tools.core.tool import BaseTool
|
|
|
|
|
|
class DirectivePayload(BaseModel):
|
|
"""工具执行产生的副作用控制流指令载荷"""
|
|
|
|
name: str
|
|
"""指令名称,对应 directive_manager 中的注册名"""
|
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
"""传递给指令处理器的具体数据"""
|
|
|
|
|
|
class ToolResult(BaseModel):
|
|
"""结构化的工具执行结果模型"""
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
output: Any = Field(...)
|
|
"""大模型实际看到的执行结果,可以是字符串、字典等序列化对象。"""
|
|
usage: UsageInfo | None = Field(default=None)
|
|
"""子智能体或复杂工具消耗的 Token 统计,将向主流程冒泡累加。"""
|
|
is_error: bool = Field(default=False)
|
|
"""是否发生了业务级别的错误"""
|
|
is_retryable: bool = Field(default=True)
|
|
"""标记该错误是否允许大模型进行自愈反思重试"""
|
|
directive: DirectivePayload | None = Field(default=None)
|
|
"""工具执行产生的副作用指令(如移交、结束运行等),供底层的指令路由引擎调度"""
|
|
|
|
def as_error(self, is_retryable: bool = True) -> "ToolResult":
|
|
"""链式方法:标记此结果为错误,并引导大模型在下一轮进行重试自愈"""
|
|
self.is_error = True
|
|
self.is_retryable = is_retryable
|
|
return self
|
|
|
|
def as_fatal(self) -> "ToolResult":
|
|
"""链式方法:标记此结果为致命错误,立即中断大模型的思考"""
|
|
self.is_error = True
|
|
self.is_retryable = False
|
|
return self
|
|
|
|
|
|
class EndRunResult(ToolResult):
|
|
"""强制结束运行并返回结果给用户"""
|
|
|
|
def __init__(self, output: Any, **kwargs):
|
|
super().__init__(
|
|
output=output,
|
|
directive=DirectivePayload(name="end_run", payload={"output": output}),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class HandoffResult(ToolResult):
|
|
"""触发智能体控制权物理移交"""
|
|
|
|
def __init__(self, target: str, reason: str = "", context_data: Any = "", **kwargs):
|
|
super().__init__(
|
|
output=f"已触发控制权移交 -> {target}。原因: {reason}",
|
|
directive=DirectivePayload(
|
|
name="handoff",
|
|
payload={
|
|
"target": target,
|
|
"reason": reason,
|
|
"context_data": context_data,
|
|
},
|
|
),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class StructuredSubmissionResult(ToolResult):
|
|
"""提交结构化解析结果并结束运行"""
|
|
|
|
def __init__(self, output: Any, parsed_obj: Any, **kwargs):
|
|
super().__init__(
|
|
output=output,
|
|
directive=DirectivePayload(
|
|
name="submit_structured", payload={"parsed_obj": parsed_obj}
|
|
),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class StateSyncResult(ToolResult):
|
|
"""
|
|
状态同步结果模型。
|
|
除了返回工具输出外,允许开发者向大模型发送一条“系统通知”,系统会自动将其追加到上下文中,防止大模型产生幻觉。
|
|
"""
|
|
|
|
state_notice: str | None = Field(default=None)
|
|
"""状态同步通知文本,将被自动转化为 SystemPrompt 发送给大模型。"""
|
|
|
|
def with_state_notice(self, notice: str) -> "StateSyncResult":
|
|
"""链式方法:设置状态同步通知"""
|
|
self.state_notice = notice
|
|
return self
|
|
|
|
|
|
class ToolResultChunk(BaseModel):
|
|
"""流式工具执行结果片段模型"""
|
|
|
|
content: str = Field(...)
|
|
"""流式输出的文本片段"""
|
|
status: str = Field(default="running")
|
|
"""当前状态 (如 running, finished)"""
|
|
metadata: dict[str, Any] | None = Field(default=None)
|
|
"""携带的附加数据 (如进度比例、图片等)"""
|
|
|
|
|
|
class ToolOptions(BaseModel):
|
|
"""工具的高阶配置选项"""
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
silent: bool = Field(default=False)
|
|
"""是否静默执行,工具执行过程与结果不会作为界面流渲染给用户。"""
|
|
strict: bool = Field(default=False)
|
|
"""是否开启严格的 JSON Schema 验证模式,开启后大模型的参数将不接受额外属性。"""
|
|
max_usage_count: int | None = Field(default=None)
|
|
"""单次 Agent 会话中的最大允许调用次数,用于防止大模型陷入死循环调用。"""
|
|
capabilities: list[Any] = Field(default_factory=list)
|
|
"""当前工具专属的生命周期拦截器 (Capabilities) 列表。"""
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
"""额外扩展元数据字典,可供其他系统或自定义中间件读取。"""
|
|
sandbox_requirements: dict[str, list[str]] | None = Field(default=None)
|
|
"""声明该工具在沙箱中执行时的环境依赖要求。"""
|
|
tags: list[str] = Field(default_factory=list)
|
|
"""用于智能字符串路由和能力聚合的标签列表。"""
|
|
max_retries: int | None = Field(default=None)
|
|
"""工具级别的局部重试上限。优先级高于全局配置。"""
|
|
args_schema: type[BaseModel] | None = Field(default=None)
|
|
"""工具的 Pydantic 数据模型约束。大模型将以此 Schema 输出 JSON。"""
|
|
require_intent: bool = Field(default=False)
|
|
"""是否强制要求大模型在调用此工具时提供意图 (_intent)。
|
|
有助于减少幻觉和提高调用准确率。
|
|
"""
|
|
concurrency: Literal["shared", "exclusive"] = Field(default="shared")
|
|
"""工具在批量调用时的并发策略。
|
|
shared 可与其他 shared 并行,exclusive 会阻塞前后工具的执行。
|
|
"""
|
|
|
|
def merge(self, other: "ToolOptions | None") -> "ToolOptions":
|
|
"""组合模式底层:合并另一个 ToolOptions,other 中的非默认值将覆盖当前值"""
|
|
if not other:
|
|
return self
|
|
merged_data = model_dump(self, exclude_unset=False)
|
|
other_data = model_dump(other, exclude_unset=True)
|
|
|
|
if other.capabilities:
|
|
merged_data["capabilities"] = self.capabilities + other.capabilities
|
|
if other.metadata:
|
|
merged_data["metadata"] = {**self.metadata, **other.metadata}
|
|
if other.tags:
|
|
merged_data["tags"] = list(set(self.tags + other.tags))
|
|
|
|
for k, v in other_data.items():
|
|
if k not in ("capabilities", "metadata", "tags"):
|
|
merged_data[k] = v
|
|
return model_validate(ToolOptions, merged_data)
|
|
|
|
|
|
class ToolkitConfig(BaseModel):
|
|
"""工具箱全局配置对象 (用于声明式配置解析)"""
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
prefix: str | None = Field(default=None)
|
|
"""工具名称前缀"""
|
|
include: list[str] | None = Field(default=None)
|
|
"""允许注册的工具名白名单"""
|
|
exclude: list[str] | None = Field(default=None)
|
|
"""排除注册的工具名黑名单"""
|
|
shared_options: ToolOptions | None = Field(default=None)
|
|
"""所有子工具默认继承的高阶配置项"""
|
|
|
|
|
|
class ToolOverride(BaseModel):
|
|
"""工具配置动态覆盖载体"""
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
name: str
|
|
"""要覆盖的目标工具名称(在全局注册表或 Provider 中的原始名称)"""
|
|
|
|
new_name: str | None = None
|
|
"""克隆后的新工具名称。如果为空,则保持原名称"""
|
|
|
|
description: str | None = None
|
|
"""覆盖后的新描述。大模型将根据此新描述决定工具调用时机"""
|
|
|
|
options: ToolOptions | None = None
|
|
"""用于覆盖该工具底层行为的高阶配置项"""
|
|
|
|
def to_tool_options(self) -> ToolOptions:
|
|
return self.options or ToolOptions()
|
|
|
|
async def resolve(self, context: RunContext | None = None) -> "ResolvedToolPayload":
|
|
from zhenxun.services.ai.tools.engine.registry import tool_provider_manager
|
|
from zhenxun.services.ai.tools.models import ResolvedToolPayload
|
|
from zhenxun.services.log import logger
|
|
|
|
found_tools = await tool_provider_manager.resolve_specific_tools([self.name])
|
|
if found_tools:
|
|
base_tool = found_tools[0]
|
|
if hasattr(base_tool, "clone_with_options"):
|
|
cloned_tool = base_tool.clone_with_options(self)
|
|
if hasattr(cloned_tool, "resolve"):
|
|
return await cloned_tool.resolve(context)
|
|
return ResolvedToolPayload(tools=[cloned_tool])
|
|
else:
|
|
logger.warning(f"工具 {self.name} 不支持动态覆盖,将原样装配。")
|
|
if hasattr(base_tool, "resolve"):
|
|
return await base_tool.resolve(context)
|
|
return ResolvedToolPayload(tools=[base_tool])
|
|
|
|
logger.warning(f"ToolOverride 找不到目标基础工具: {self.name}")
|
|
return ResolvedToolPayload()
|
|
|
|
|
|
class GlobalToolFilter(BaseModel):
|
|
"""全局宏观工具过滤器"""
|
|
|
|
allowed_servers: list[str] | None = None
|
|
"""仅允许的服务端名称列表"""
|
|
excluded_servers: list[str] | None = None
|
|
"""需要排除的服务端名称列表"""
|
|
|
|
|
|
class ValidatedToolCall(BaseModel):
|
|
"""工具调用验证结果载体(解耦验证与执行)"""
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
call: ToolCallPart = Field(...)
|
|
"""原始工具调用部件"""
|
|
tool: Any | None = Field(default=None)
|
|
"""匹配到的目标工具实例"""
|
|
args_valid: bool = Field(default=False)
|
|
"""参数是否成功通过校验"""
|
|
validated_args: dict[str, Any] | None = Field(default=None)
|
|
"""通过验证并反序列化后的参数字典"""
|
|
validation_error: BaseException | None = Field(default=None)
|
|
"""验证失败时的异常信息"""
|
|
intent: str | None = Field(default=None)
|
|
"""从参数中剥离出的大模型调用意图 (_intent)"""
|
|
|
|
|
|
class Query(BaseModel):
|
|
"""
|
|
工具的声明式查询对象。
|
|
用于在 Agent 中精确或批量筛选加载特定命名空间、特定标签的工具。
|
|
"""
|
|
|
|
name: str | None = Field(default=None)
|
|
"""如果提供,则必须与工具的名称完全一致。"""
|
|
tags: list[str] | None = Field(default=None)
|
|
"""如果提供,则工具必须包含这里列出的所有标签 (交集/AND匹配)。"""
|
|
namespace: str | None = Field(default=None)
|
|
"""必填(由系统补充或显式声明)。限制搜索的插件命名空间,'global' 将跨全插件搜索。"""
|
|
metadata_filter: dict[str, Any] | None = Field(default=None)
|
|
"""如果提供,则工具的 metadata 必须包含这里列出的所有键值对。"""
|
|
|
|
def match(self, tool: "BaseTool") -> bool:
|
|
"""判断某个工具或工具箱是否符合当前 Query 的筛选条件"""
|
|
if self.name:
|
|
tool_name = getattr(tool, "name", getattr(tool, "__class__", type).__name__)
|
|
if tool_name != self.name:
|
|
return False
|
|
if self.tags:
|
|
tool_config = getattr(tool, "config", None)
|
|
if (
|
|
tool_config
|
|
and hasattr(tool_config, "shared_options")
|
|
and tool_config.shared_options
|
|
):
|
|
tool_tags = tool_config.shared_options.tags
|
|
else:
|
|
tool_settings = getattr(tool, "settings", None)
|
|
tool_tags = getattr(tool_settings, "tags", []) if tool_settings else []
|
|
if not all(tag in tool_tags for tag in self.tags):
|
|
return False
|
|
|
|
if self.metadata_filter:
|
|
tool_settings = getattr(tool, "settings", None)
|
|
tool_metadata = (
|
|
getattr(tool_settings, "metadata", getattr(tool, "metadata", {}))
|
|
if tool_settings
|
|
else getattr(tool, "metadata", {})
|
|
)
|
|
for k, v in self.metadata_filter.items():
|
|
if tool_metadata.get(k) != v:
|
|
return False
|
|
return True
|
|
|
|
|
|
@dataclass
|
|
class ResolvedToolPayload:
|
|
"""解析后的工具上下文包"""
|
|
|
|
tools: list[Any] = field(default_factory=list)
|
|
injected_prompts: list[str] = field(default_factory=list)
|
|
toolkits: list[Any] = field(default_factory=list)
|
|
|
|
|
|
__all__ = [
|
|
"GlobalToolFilter",
|
|
"Query",
|
|
"ResolvedToolPayload",
|
|
"ToolOptions",
|
|
"ToolOverride",
|
|
"ToolResult",
|
|
"ToolResultChunk",
|
|
"ToolkitConfig",
|
|
"ValidatedToolCall",
|
|
]
|