mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-13 07:50:00 +08:00
✨ 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>
This commit is contained in:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
bdc1374848
commit
80fc5b86a7
@@ -1,120 +1,387 @@
|
||||
"""
|
||||
LLM 服务模块 - 公共 API 入口
|
||||
向下兼容门面 (Facade)
|
||||
|
||||
提供统一的 AI 服务调用接口、核心类型定义和模型管理功能。
|
||||
利用 sys.modules 魔法,将对 zhenxun.services.llm 及其子模块的导入
|
||||
无缝重定向到 zhenxun.services.ai.llm 下,避免破坏任何第三方插件。
|
||||
同时在此提供最常用的类导出,作为 LLM 服务的统一入口。
|
||||
"""
|
||||
|
||||
from .api import (
|
||||
chat,
|
||||
code,
|
||||
create_image,
|
||||
embed,
|
||||
embed_documents,
|
||||
embed_query,
|
||||
generate,
|
||||
generate_structured,
|
||||
search,
|
||||
)
|
||||
from .config import (
|
||||
CommonOverrides,
|
||||
GenConfigBuilder,
|
||||
LLMGenerationConfig,
|
||||
register_llm_configs,
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
from zhenxun.services import logger
|
||||
|
||||
logger.warning(
|
||||
"zhenxun.services.llm 已被重构迁移至 zhenxun.services.ai.llm,"
|
||||
"为了更好的性能和兼容性,请及时更新您的插件导入路径。"
|
||||
)
|
||||
|
||||
register_llm_configs()
|
||||
from .api import ModelName
|
||||
from .manager import (
|
||||
clear_model_cache,
|
||||
get_cache_stats,
|
||||
get_global_default_model_name,
|
||||
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,
|
||||
list_model_identifiers,
|
||||
set_global_default_model_name,
|
||||
)
|
||||
from .memory import (
|
||||
AIConfig,
|
||||
BaseMemory,
|
||||
MemoryProcessor,
|
||||
set_default_memory_backend,
|
||||
)
|
||||
from .session import AI
|
||||
from .tools import RunContext, ToolInvoker, function_tool, tool_provider_manager
|
||||
from .types import (
|
||||
EmbeddingTaskType,
|
||||
LLMContentPart,
|
||||
LLMErrorCode,
|
||||
LLMException,
|
||||
LLMMessage,
|
||||
LLMResponse,
|
||||
ModelDetail,
|
||||
ModelInfo,
|
||||
ModelProvider,
|
||||
ResponseFormat,
|
||||
TaskType,
|
||||
ToolCategory,
|
||||
ToolMetadata,
|
||||
UsageInfo,
|
||||
)
|
||||
from .types.models import (
|
||||
GeminiCodeExecution,
|
||||
GeminiGoogleSearch,
|
||||
GeminiUrlContext,
|
||||
)
|
||||
from .utils import create_multimodal_message, message_to_unimessage, unimsg_to_llm_parts
|
||||
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",
|
||||
"BaseMemory",
|
||||
"CommonOverrides",
|
||||
"EmbeddingTaskType",
|
||||
"GeminiCodeExecution",
|
||||
"GeminiGoogleSearch",
|
||||
"GeminiUrlContext",
|
||||
"GenConfigBuilder",
|
||||
"GenerationConfig",
|
||||
"LLMContentPart",
|
||||
"LLMErrorCode",
|
||||
"LLMException",
|
||||
"LLMGenerationConfig",
|
||||
"LLMMessage",
|
||||
"LLMResponse",
|
||||
"MemoryProcessor",
|
||||
"ModelDetail",
|
||||
"ModelInfo",
|
||||
"ModelName",
|
||||
"ModelProvider",
|
||||
"ResponseFormat",
|
||||
"RunContext",
|
||||
"TaskType",
|
||||
"ToolCategory",
|
||||
"LLMToolCall",
|
||||
"LLMToolFunction",
|
||||
"OutputConfig",
|
||||
"ToolDefinition",
|
||||
"ToolExecutable",
|
||||
"ToolInvoker",
|
||||
"ToolMetadata",
|
||||
"UsageInfo",
|
||||
"chat",
|
||||
"clear_model_cache",
|
||||
"code",
|
||||
"create_image",
|
||||
"create_multimodal_message",
|
||||
"embed",
|
||||
"embed_documents",
|
||||
"embed_query",
|
||||
"ToolResult",
|
||||
"function_tool",
|
||||
"generate",
|
||||
"generate_structured",
|
||||
"get_cache_stats",
|
||||
"get_global_default_model_name",
|
||||
"get_default_model",
|
||||
"get_model_instance",
|
||||
"list_available_models",
|
||||
"list_embedding_models",
|
||||
"list_model_identifiers",
|
||||
"message_to_unimessage",
|
||||
"register_llm_configs",
|
||||
"search",
|
||||
"set_default_memory_backend",
|
||||
"set_global_default_model_name",
|
||||
"tool_provider_manager",
|
||||
"unimsg_to_llm_parts",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user