Files
zhenxun_bot/zhenxun/services/ai/tools/models.py
T
80fc5b86a7 ✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 (#2146)
* ✨ 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>
2026-07-03 08:53:56 +08:00

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",
]