mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 08:39: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>
388 lines
12 KiB
Python
388 lines
12 KiB
Python
"""
|
||
向下兼容门面 (Facade)
|
||
|
||
利用 sys.modules 魔法,将对 zhenxun.services.llm 及其子模块的导入
|
||
无缝重定向到 zhenxun.services.ai.llm 下,避免破坏任何第三方插件。
|
||
同时在此提供最常用的类导出,作为 LLM 服务的统一入口。
|
||
"""
|
||
|
||
import sys
|
||
from types import ModuleType
|
||
from typing import Any
|
||
|
||
from zhenxun.services import logger
|
||
|
||
logger.warning(
|
||
"zhenxun.services.llm 已被重构迁移至 zhenxun.services.ai.llm,"
|
||
"为了更好的性能和兼容性,请及时更新您的插件导入路径。"
|
||
)
|
||
|
||
from pydantic import BaseModel, ConfigDict
|
||
|
||
import zhenxun.services.ai.core
|
||
import zhenxun.services.ai.core.exceptions
|
||
import zhenxun.services.ai.core.messages
|
||
from zhenxun.services.ai.core.messages import ChatResponse as LLMResponse
|
||
from zhenxun.services.ai.core.messages import LLMContentPart, ToolCallPart
|
||
import zhenxun.services.ai.core.models
|
||
from zhenxun.services.ai.core.models import ToolDefinition
|
||
import zhenxun.services.ai.core.options
|
||
import zhenxun.services.ai.core.protocols
|
||
from zhenxun.services.ai.core.protocols.tool import ToolExecutable
|
||
import zhenxun.services.ai.llm
|
||
import zhenxun.services.ai.llm.system.capabilities
|
||
from zhenxun.services.ai.run import RunContext as RealRunContext
|
||
import zhenxun.services.ai.tools
|
||
from zhenxun.services.ai.tools.models import ToolResult
|
||
|
||
_legacy_types_module = ModuleType("zhenxun.services.llm.types")
|
||
for _mod in (
|
||
zhenxun.services.ai.core.options,
|
||
zhenxun.services.ai.core.exceptions,
|
||
zhenxun.services.ai.core.messages,
|
||
zhenxun.services.ai.core.models,
|
||
):
|
||
for _name in dir(_mod):
|
||
if not _name.startswith("_"):
|
||
setattr(_legacy_types_module, _name, getattr(_mod, _name))
|
||
|
||
sys.modules["zhenxun.services.llm.types"] = _legacy_types_module
|
||
sys.modules["zhenxun.services.llm.types.models"] = zhenxun.services.ai.core.models
|
||
sys.modules["zhenxun.services.llm.types.protocols"] = zhenxun.services.ai.core.protocols
|
||
sys.modules["zhenxun.services.llm.types.exceptions"] = (
|
||
zhenxun.services.ai.core.exceptions
|
||
)
|
||
sys.modules["zhenxun.services.llm.types.capabilities"] = (
|
||
zhenxun.services.ai.llm.system.capabilities
|
||
)
|
||
|
||
prefix_old = "zhenxun.services.llm"
|
||
prefix_new = "zhenxun.services.ai.llm"
|
||
|
||
for module_name, module_obj in list(sys.modules.items()):
|
||
if module_name.startswith(prefix_new):
|
||
old_module_name = module_name.replace(prefix_new, prefix_old, 1)
|
||
if old_module_name not in sys.modules:
|
||
sys.modules[old_module_name] = module_obj
|
||
|
||
sys.modules["zhenxun.services.llm.tools"] = zhenxun.services.ai.tools
|
||
|
||
|
||
class LLMToolFunction(BaseModel):
|
||
name: str
|
||
arguments: str
|
||
|
||
|
||
class LLMToolCall(BaseModel):
|
||
id: str
|
||
function: LLMToolFunction
|
||
thought_signature: str | None = None
|
||
type: str = "function"
|
||
|
||
|
||
class _FakeFunction:
|
||
def __init__(self, name, args):
|
||
self.name = name
|
||
self.arguments = (
|
||
args
|
||
if isinstance(args, str)
|
||
else __import__("json").dumps(args, ensure_ascii=False)
|
||
)
|
||
|
||
|
||
@property
|
||
def _legacy_function(self) -> _FakeFunction:
|
||
return _FakeFunction(self.tool_name, self.args)
|
||
|
||
|
||
setattr(ToolCallPart, "function", _legacy_function) # type: ignore
|
||
|
||
|
||
@property
|
||
def _legacy_thought_signature(self) -> Any:
|
||
return self.metadata.get("thought_signature") if self.metadata else None
|
||
|
||
|
||
@_legacy_thought_signature.setter
|
||
def _legacy_thought_signature(self, value: Any) -> None:
|
||
if self.metadata is None:
|
||
self.metadata = {}
|
||
self.metadata["thought_signature"] = value
|
||
|
||
|
||
setattr(ToolCallPart, "thought_signature", _legacy_thought_signature) # type: ignore
|
||
|
||
from zhenxun.services.ai.core.messages import LLMMessage
|
||
|
||
_original_assistant_tool_calls = LLMMessage.assistant_tool_calls
|
||
|
||
|
||
@classmethod
|
||
def _shim_assistant_tool_calls(
|
||
cls, tool_calls: Any, content: Any = "", scope: Any = None
|
||
) -> Any:
|
||
converted = []
|
||
for tc in tool_calls:
|
||
if hasattr(tc, "function") and not isinstance(tc, ToolCallPart):
|
||
converted.append(
|
||
ToolCallPart(
|
||
id=tc.id, tool_name=tc.function.name, args=tc.function.arguments
|
||
)
|
||
)
|
||
else:
|
||
converted.append(tc)
|
||
return _original_assistant_tool_calls(converted, content, scope) # type: ignore
|
||
|
||
|
||
setattr(LLMMessage, "assistant_tool_calls", _shim_assistant_tool_calls) # type: ignore
|
||
|
||
if not hasattr(LLMMessage, "name"):
|
||
|
||
@property
|
||
def _legacy_name(self) -> Any:
|
||
return getattr(self, "source_name", None)
|
||
|
||
@_legacy_name.setter
|
||
def _legacy_name(self, value: Any) -> None:
|
||
self.source_name = value
|
||
|
||
setattr(LLMMessage, "name", _legacy_name) # type: ignore
|
||
|
||
if not hasattr(LLMMessage, "tool_call_id"):
|
||
|
||
@property
|
||
def _legacy_tool_call_id(self) -> Any:
|
||
return None
|
||
|
||
@_legacy_tool_call_id.setter
|
||
def _legacy_tool_call_id(self, value: Any) -> None:
|
||
pass
|
||
|
||
setattr(LLMMessage, "tool_call_id", _legacy_tool_call_id) # type: ignore
|
||
|
||
from zhenxun.services.ai.core.options import GenerationConfig, ReasoningEffort
|
||
|
||
mod_config_gen = ModuleType("zhenxun.services.llm.config.generation")
|
||
sys.modules["zhenxun.services.llm.config"] = ModuleType("zhenxun.services.llm.config")
|
||
sys.modules["zhenxun.services.llm.config.generation"] = mod_config_gen
|
||
|
||
|
||
class ReasoningConfig(BaseModel):
|
||
model_config = ConfigDict(extra="allow") # type: ignore
|
||
|
||
|
||
class ToolConfig(BaseModel):
|
||
model_config = ConfigDict(extra="allow") # type: ignore
|
||
|
||
|
||
setattr(mod_config_gen, "LLMGenerationConfig", GenerationConfig) # type: ignore
|
||
setattr(mod_config_gen, "ReasoningConfig", ReasoningConfig) # type: ignore
|
||
setattr(mod_config_gen, "ToolConfig", ToolConfig) # type: ignore
|
||
setattr(mod_config_gen, "ReasoningEffort", ReasoningEffort) # type: ignore
|
||
|
||
_target_models_mod = sys.modules["zhenxun.services.llm.types.models"]
|
||
setattr(_target_models_mod, "ToolResult", ToolResult) # type: ignore
|
||
setattr(_target_models_mod, "LLMToolCall", LLMToolCall) # type: ignore
|
||
setattr(_target_models_mod, "LLMToolFunction", LLMToolFunction) # type: ignore
|
||
setattr(_target_models_mod, "LLMContentPart", LLMContentPart) # type: ignore
|
||
setattr(_target_models_mod, "ToolDefinition", ToolDefinition) # type: ignore
|
||
setattr(_target_models_mod, "LLMResponse", LLMResponse) # type: ignore
|
||
setattr(_target_models_mod, "LLMMessage", LLMMessage) # type: ignore
|
||
setattr(
|
||
sys.modules["zhenxun.services.llm.types.protocols"],
|
||
"ToolExecutable",
|
||
ToolExecutable,
|
||
) # type: ignore
|
||
|
||
|
||
class LegacyRunContextShim:
|
||
"""拦截旧版 RunContext(extra={...}) 的调用,转化为新版支持的 state"""
|
||
|
||
def __new__(cls, session_id=None, extra=None, scope=None, **kwargs):
|
||
state = kwargs.get("state", {})
|
||
if extra:
|
||
state.update(extra)
|
||
if scope:
|
||
state.update(scope)
|
||
ctx = RealRunContext(session_id=session_id, state=state)
|
||
ctx.extra = ctx.state # type: ignore
|
||
ctx.scope = ctx.state # type: ignore
|
||
return ctx
|
||
|
||
|
||
setattr(sys.modules["zhenxun.services.llm.tools"], "RunContext", LegacyRunContextShim) # type: ignore
|
||
|
||
|
||
class ToolInvoker:
|
||
"""向下兼容垫片:代替被重构移除的旧版 ToolInvoker"""
|
||
|
||
def __init__(self, callbacks=None):
|
||
self.callbacks = callbacks or []
|
||
|
||
async def execute_tool_call(self, tool_call, available_tools, context=None):
|
||
import json
|
||
|
||
if hasattr(tool_call, "function") and hasattr(tool_call.function, "name"):
|
||
tool_name = tool_call.function.name
|
||
args_raw = tool_call.function.arguments
|
||
else:
|
||
tool_name = getattr(tool_call, "tool_name", "unknown")
|
||
args_raw = getattr(tool_call, "args", "{}")
|
||
|
||
arguments = {}
|
||
if args_raw:
|
||
if isinstance(args_raw, str):
|
||
try:
|
||
arguments = json.loads(args_raw)
|
||
except Exception:
|
||
pass
|
||
elif isinstance(args_raw, dict):
|
||
arguments = args_raw
|
||
|
||
executable = available_tools.get(tool_name)
|
||
if not executable:
|
||
return tool_call, ToolResult(output=f"Error: Tool '{tool_name}' not found.")
|
||
|
||
try:
|
||
result = await executable.execute(context=context, **arguments)
|
||
if not hasattr(result, "output"):
|
||
result = ToolResult(output=result)
|
||
return tool_call, result
|
||
except Exception as e:
|
||
return tool_call, ToolResult(output=f"System Execution Error: {e!s}")
|
||
|
||
|
||
import zhenxun.services.ai.tools
|
||
|
||
setattr(zhenxun.services.ai.tools, "ToolInvoker", ToolInvoker) # type: ignore
|
||
setattr(sys.modules["zhenxun.services.llm.tools"], "ToolInvoker", ToolInvoker) # type: ignore
|
||
|
||
|
||
class CommonOverrides:
|
||
"""向下兼容垫片:由于该类已被废弃,此处提供空实现以防止旧插件导入报错。"""
|
||
|
||
@staticmethod
|
||
def _fallback(*args, **kwargs):
|
||
from zhenxun.services.ai.llm.builder import IntentBuilder
|
||
|
||
return IntentBuilder().build()
|
||
|
||
gemini_json = _fallback
|
||
gemini_2_5_thinking = _fallback
|
||
gemini_3_thinking = _fallback
|
||
gemini_structured = _fallback
|
||
gemini_safe = _fallback
|
||
gemini_code_execution = _fallback
|
||
gemini_grounding = _fallback
|
||
gemini_nano_banana = _fallback
|
||
gemini_high_res = _fallback
|
||
|
||
|
||
from zhenxun.services.ai.core.options import GenerationConfig, OutputFormatConfig
|
||
|
||
|
||
class OutputConfig(OutputFormatConfig):
|
||
"""向下兼容垫片:旧版 OutputConfig 等价于 OutputFormatConfig。"""
|
||
|
||
|
||
AIConfig = GenerationConfig
|
||
LLMGenerationConfig = GenerationConfig
|
||
from zhenxun.services.ai.llm import * # noqa: F403
|
||
from zhenxun.services.ai.llm.manager import (
|
||
get_default_model,
|
||
get_model_instance,
|
||
list_available_models,
|
||
list_embedding_models,
|
||
)
|
||
from zhenxun.services.ai.message_builder import MessageBuilder
|
||
|
||
message_to_unimessage = MessageBuilder.message_to_unimessage
|
||
unimsg_to_llm_parts = MessageBuilder.unimsg_to_llm_parts
|
||
from zhenxun.services.ai.core.exceptions import LLMException
|
||
from zhenxun.services.ai.core.messages import ChatRequest
|
||
from zhenxun.services.ai.core.messages import ChatResponse as LLMResponse
|
||
from zhenxun.services.ai.llm.api import generate_structured as _new_generate_structured
|
||
from zhenxun.services.ai.llm.engine.router import LLMOrchestrator
|
||
from zhenxun.services.ai.tools import tool as function_tool
|
||
|
||
|
||
class AI:
|
||
"""向下兼容垫片:代替被彻底删除的旧版 AI 类"""
|
||
|
||
def __init__(self, session_id=None, **kwargs):
|
||
self.session_id = session_id
|
||
|
||
async def generate_internal(
|
||
self,
|
||
messages,
|
||
model=None,
|
||
config=None,
|
||
tools=None,
|
||
tool_choice=None,
|
||
timeout=None,
|
||
):
|
||
req = ChatRequest(
|
||
messages=messages,
|
||
config=config,
|
||
tools=tools,
|
||
tool_choice=tool_choice,
|
||
timeout=timeout,
|
||
)
|
||
if self.session_id:
|
||
req.extra["session_id"] = self.session_id
|
||
return await LLMOrchestrator.invoke(req, model_name=model, task="chat")
|
||
|
||
async def generate_structured(
|
||
self,
|
||
message,
|
||
response_model,
|
||
model=None,
|
||
tools=None,
|
||
tool_choice=None,
|
||
instruction=None,
|
||
timeout=None,
|
||
template_vars=None,
|
||
config=None,
|
||
max_validation_retries=None,
|
||
validation_callback=None,
|
||
error_prompt_template=None,
|
||
auto_thinking=False,
|
||
):
|
||
return await _new_generate_structured(
|
||
message=message,
|
||
response_model=response_model,
|
||
model=model,
|
||
instruction=instruction,
|
||
timeout=timeout,
|
||
config=config,
|
||
max_retries=max_validation_retries,
|
||
error_prompt_template=error_prompt_template,
|
||
)
|
||
|
||
|
||
__all__ = [
|
||
"AI",
|
||
"AIConfig",
|
||
"CommonOverrides",
|
||
"GenerationConfig",
|
||
"LLMContentPart",
|
||
"LLMException",
|
||
"LLMGenerationConfig",
|
||
"LLMMessage",
|
||
"LLMResponse",
|
||
"LLMToolCall",
|
||
"LLMToolFunction",
|
||
"OutputConfig",
|
||
"ToolDefinition",
|
||
"ToolExecutable",
|
||
"ToolInvoker",
|
||
"ToolResult",
|
||
"function_tool",
|
||
"get_default_model",
|
||
"get_model_instance",
|
||
"list_available_models",
|
||
"list_embedding_models",
|
||
"message_to_unimessage",
|
||
"unimsg_to_llm_parts",
|
||
]
|