mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-28 16:20:56 +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>
316 lines
12 KiB
Python
316 lines
12 KiB
Python
import asyncio
|
|
from collections.abc import Iterable
|
|
import contextlib
|
|
from typing import Any, ClassVar
|
|
from typing_extensions import Self
|
|
|
|
from tortoise.backends.base.client import BaseDBAsyncClient
|
|
from tortoise.exceptions import (
|
|
IntegrityError,
|
|
MultipleObjectsReturned,
|
|
TransactionManagementError,
|
|
)
|
|
from tortoise.models import Model as TortoiseModel
|
|
from tortoise.transactions import in_transaction
|
|
|
|
from zhenxun.services.cache import CacheRoot
|
|
from zhenxun.services.log import logger
|
|
from zhenxun.utils.enum import DbLockType
|
|
|
|
from .config import LOG_COMMAND, db_model
|
|
from .utils import with_db_timeout
|
|
|
|
|
|
class Model(TortoiseModel):
|
|
"""
|
|
增强的ORM基类,解决锁嵌套问题
|
|
"""
|
|
|
|
sem_data: ClassVar[dict[str, dict[str, asyncio.Semaphore]]] = {}
|
|
_current_locks: ClassVar[dict[tuple[str, int], DbLockType]] = {}
|
|
|
|
def __init_subclass__(cls, **kwargs):
|
|
super().__init_subclass__(**kwargs)
|
|
|
|
is_abstract = (
|
|
getattr(cls.Meta, "abstract", False) if hasattr(cls, "Meta") else False
|
|
)
|
|
if not is_abstract and cls.__module__ not in db_model.models:
|
|
db_model.models.append(cls.__module__)
|
|
|
|
if func := getattr(cls, "_run_script", None):
|
|
db_model.script_method.append((cls.__module__, func))
|
|
|
|
@classmethod
|
|
def get_cache_type(cls) -> str | None:
|
|
"""获取缓存类型"""
|
|
return getattr(cls, "cache_type", None)
|
|
|
|
@classmethod
|
|
def get_cache_key_field(cls) -> str | tuple[str]:
|
|
"""获取缓存键字段"""
|
|
return getattr(cls, "cache_key_field", "id")
|
|
|
|
@classmethod
|
|
def get_cache_key(cls, instance) -> str | None:
|
|
"""获取缓存键
|
|
|
|
参数:
|
|
instance: 模型实例
|
|
|
|
返回:
|
|
str | None: 缓存键,如果无法获取则返回None
|
|
"""
|
|
from zhenxun.services.cache.config import COMPOSITE_KEY_SEPARATOR
|
|
|
|
key_field = cls.get_cache_key_field()
|
|
|
|
if isinstance(key_field, tuple):
|
|
# 多字段主键
|
|
key_parts = []
|
|
for field in key_field:
|
|
if hasattr(instance, field):
|
|
value = getattr(instance, field, None)
|
|
key_parts.append(value if value is not None else "")
|
|
else:
|
|
# 如果缺少任何必要的字段,返回None
|
|
key_parts.append("")
|
|
|
|
# 如果没有有效参数,返回None
|
|
return COMPOSITE_KEY_SEPARATOR.join(key_parts) if key_parts else None
|
|
elif hasattr(instance, key_field):
|
|
value = getattr(instance, key_field, None)
|
|
return str(value) if value is not None else None
|
|
|
|
return None
|
|
|
|
@classmethod
|
|
def get_semaphore(cls, lock_type: DbLockType):
|
|
enable_lock = getattr(cls, "enable_lock", None)
|
|
if not enable_lock or lock_type not in enable_lock:
|
|
return None
|
|
|
|
if cls.__name__ not in cls.sem_data:
|
|
cls.sem_data[cls.__name__] = {}
|
|
if lock_type not in cls.sem_data[cls.__name__]:
|
|
cls.sem_data[cls.__name__][lock_type] = asyncio.Semaphore(1)
|
|
return cls.sem_data[cls.__name__][lock_type]
|
|
|
|
@classmethod
|
|
def _require_lock(cls, lock_type: DbLockType) -> bool:
|
|
"""检查是否需要真正加锁"""
|
|
lock_key = (cls.__name__, id(asyncio.current_task()))
|
|
return cls._current_locks.get(lock_key) != lock_type
|
|
|
|
@classmethod
|
|
@contextlib.asynccontextmanager
|
|
async def _lock_context(cls, lock_type: DbLockType):
|
|
"""带重入检查的锁上下文"""
|
|
lock_key = (cls.__name__, id(asyncio.current_task()))
|
|
need_lock = cls._require_lock(lock_type)
|
|
|
|
if need_lock and (sem := cls.get_semaphore(lock_type)):
|
|
cls._current_locks[lock_key] = lock_type
|
|
async with sem:
|
|
yield
|
|
cls._current_locks.pop(lock_key, None)
|
|
else:
|
|
yield
|
|
|
|
@classmethod
|
|
async def create(
|
|
cls, using_db: BaseDBAsyncClient | None = None, **kwargs: Any
|
|
) -> Self:
|
|
"""创建数据(使用CREATE锁)"""
|
|
async with cls._lock_context(DbLockType.CREATE):
|
|
# 直接调用父类的_create方法避免触发save的锁
|
|
result = await super().create(using_db=using_db, **kwargs)
|
|
if cache_type := cls.get_cache_type():
|
|
await CacheRoot.invalidate_cache(cache_type, cls.get_cache_key(result))
|
|
return result
|
|
|
|
@classmethod
|
|
async def get_or_create(
|
|
cls,
|
|
defaults: dict | None = None,
|
|
using_db: BaseDBAsyncClient | None = None,
|
|
**kwargs: Any,
|
|
) -> tuple[Self, bool]:
|
|
"""获取或创建数据(无锁版本,依赖数据库约束)"""
|
|
try:
|
|
result = await super().get_or_create(
|
|
defaults=defaults, using_db=using_db, **kwargs
|
|
)
|
|
except IntegrityError:
|
|
# 并发创建冲突时,回退为查询已存在记录
|
|
try:
|
|
if using_db is not None:
|
|
obj = await cls.filter(**kwargs).using_db(using_db).get()
|
|
result = (obj, False)
|
|
else:
|
|
raise TransactionManagementError("fallback to new transaction")
|
|
except TransactionManagementError:
|
|
async with in_transaction() as connection:
|
|
obj = await cls.filter(**kwargs).using_db(connection).get()
|
|
result = (obj, False)
|
|
|
|
if result[1] and (cache_type := cls.get_cache_type()):
|
|
await CacheRoot.invalidate_cache(cache_type, cls.get_cache_key(result[0]))
|
|
return result
|
|
|
|
@classmethod
|
|
async def update_or_create(
|
|
cls,
|
|
defaults: dict | None = None,
|
|
using_db: BaseDBAsyncClient | None = None,
|
|
**kwargs: Any,
|
|
) -> tuple[Self, bool]:
|
|
"""更新或创建数据(使用UPSERT锁)"""
|
|
async with cls._lock_context(DbLockType.UPSERT):
|
|
try:
|
|
# 先尝试更新(带行锁)
|
|
async with in_transaction():
|
|
if obj := await cls.filter(**kwargs).select_for_update().first():
|
|
await obj.update_from_dict(defaults or {})
|
|
await obj.save()
|
|
result = (obj, False)
|
|
else:
|
|
obj = await super().create(**kwargs, **(defaults or {}))
|
|
result = (obj, True)
|
|
|
|
if cache_type := cls.get_cache_type():
|
|
await CacheRoot.invalidate_cache(
|
|
cache_type, cls.get_cache_key(result[0])
|
|
)
|
|
return result
|
|
except IntegrityError:
|
|
# 处理极端情况下的唯一约束冲突
|
|
obj = await cls.get(**kwargs)
|
|
return obj, False
|
|
|
|
async def save(
|
|
self,
|
|
using_db: BaseDBAsyncClient | None = None,
|
|
update_fields: Iterable[str] | None = None,
|
|
force_create: bool = False,
|
|
force_update: bool = False,
|
|
):
|
|
"""保存数据(根据操作类型自动选择锁)"""
|
|
lock_type = (
|
|
DbLockType.CREATE
|
|
if getattr(self, "id", None) is None
|
|
else DbLockType.UPDATE
|
|
)
|
|
async with self._lock_context(lock_type):
|
|
await super().save(
|
|
using_db=using_db,
|
|
update_fields=update_fields,
|
|
force_create=force_create,
|
|
force_update=force_update,
|
|
)
|
|
if cache_type := getattr(self, "cache_type", None):
|
|
await CacheRoot.invalidate_cache(
|
|
cache_type, self.__class__.get_cache_key(self)
|
|
)
|
|
|
|
async def delete(self, using_db: BaseDBAsyncClient | None = None):
|
|
cache_type = getattr(self, "cache_type", None)
|
|
key = self.__class__.get_cache_key(self) if cache_type else None
|
|
# 执行删除操作
|
|
await super().delete(using_db=using_db)
|
|
|
|
# 清除缓存
|
|
if cache_type:
|
|
await CacheRoot.invalidate_cache(cache_type, key)
|
|
|
|
@classmethod
|
|
async def safe_get_or_none(
|
|
cls,
|
|
*args,
|
|
using_db: BaseDBAsyncClient | None = None,
|
|
clean_duplicates: bool = True,
|
|
**kwargs: Any,
|
|
) -> Self | None:
|
|
"""安全地获取一条记录或None,处理存在多个记录时返回最新的那个
|
|
注意,默认会删除重复的记录,仅保留最新的
|
|
|
|
参数:
|
|
*args: 查询参数
|
|
using_db: 数据库连接
|
|
clean_duplicates: 是否删除重复的记录,仅保留最新的
|
|
**kwargs: 查询参数
|
|
|
|
返回:
|
|
Self | None: 查询结果,如果不存在返回None
|
|
"""
|
|
try:
|
|
# 先尝试使用 get_or_none 获取单个记录
|
|
try:
|
|
return await with_db_timeout(
|
|
cls.get_or_none(*args, using_db=using_db, **kwargs),
|
|
operation=f"{cls.__name__}.get_or_none",
|
|
source="DataBaseModel",
|
|
)
|
|
except MultipleObjectsReturned:
|
|
# 如果出现多个记录的情况,进行特殊处理
|
|
logger.warning(
|
|
f"{cls.__name__} safe_get_or_none 发现多个记录: {kwargs}",
|
|
LOG_COMMAND,
|
|
)
|
|
|
|
# 查询所有匹配记录
|
|
records = await with_db_timeout(
|
|
cls.filter(*args, **kwargs).all(),
|
|
operation=f"{cls.__name__}.filter.all",
|
|
source="DataBaseModel",
|
|
)
|
|
|
|
if not records:
|
|
return None
|
|
|
|
# 如果需要清理重复记录
|
|
if clean_duplicates and hasattr(records[0], "id"):
|
|
# 按 id 排序
|
|
records = sorted(
|
|
records, key=lambda x: getattr(x, "id", 0), reverse=True
|
|
)
|
|
for record in records[1:]:
|
|
try:
|
|
await with_db_timeout(
|
|
record.delete(),
|
|
operation=f"{cls.__name__}.delete_duplicate",
|
|
source="DataBaseModel",
|
|
)
|
|
logger.info(
|
|
f"{cls.__name__} 删除重复记录:"
|
|
f" id={getattr(record, 'id', None)}",
|
|
LOG_COMMAND,
|
|
)
|
|
except Exception as del_e:
|
|
logger.error(f"删除重复记录失败: {del_e}")
|
|
return records[0]
|
|
# 如果不需要清理或没有 id 字段,则返回最新的记录
|
|
if hasattr(cls, "id"):
|
|
return await with_db_timeout(
|
|
cls.filter(*args, **kwargs).order_by("-id").first(),
|
|
operation=f"{cls.__name__}.filter.order_by.first",
|
|
source="DataBaseModel",
|
|
)
|
|
# 如果没有 id 字段,则返回第一个记录
|
|
return await with_db_timeout(
|
|
cls.filter(*args, **kwargs).first(),
|
|
operation=f"{cls.__name__}.filter.first",
|
|
source="DataBaseModel",
|
|
)
|
|
except asyncio.TimeoutError:
|
|
logger.error(
|
|
f"数据库操作超时: {cls.__name__}.safe_get_or_none", LOG_COMMAND
|
|
)
|
|
return None
|
|
except Exception as e:
|
|
# 其他类型的错误则继续抛出
|
|
logger.error(
|
|
f"数据库操作异常: {cls.__name__}.safe_get_or_none, {e!s}", LOG_COMMAND
|
|
)
|
|
raise
|