refactor(cache): 重构工具缓存机制并优化LLM请求重试逻辑

将工具缓存的实现从`ToolExecutor`的装饰器模式重构为直接集成。缓存逻辑被移出`cache_manager.py`并整合进`ToolExecutor.execute_tool_call`方法中,简化了代码结构并使其更易于维护。

主要变更:
- 从`cache_manager.py`中移除了`wrap_tool_executor`函数。
- 在`tool_use.py`中,`execute_tool_call`现在包含完整的缓存检查和设置逻辑。
- 调整了`llm_models/utils_model.py`中的LLM请求逻辑,为模型生成的空回复或截断响应增加了内部重试机制,增强了稳定性。
- 清理了项目中未使用的导入和过时的文档文件,以保持代码库的整洁。
This commit is contained in:
minecraft1024a
2025-08-28 20:10:32 +08:00
parent f7f39431f5
commit eb1feeeb0b
15 changed files with 199 additions and 413 deletions

View File

@@ -1,89 +0,0 @@
# 💻 Command组件详解
## 📖 什么是Command
Command是直接响应用户明确指令的组件与Action不同Command是**被动触发**的,当用户输入特定格式的命令时立即执行。
Command通过正则表达式匹配用户输入提供确定性的功能服务。
### 🎯 Command的特点
- 🎯 **确定性执行**:匹配到命令立即执行,无随机性
-**即时响应**:用户主动触发,快速响应
- 🔍 **正则匹配**:通过正则表达式精确匹配用户输入
- 🛑 **拦截控制**:可以控制是否阻止消息继续处理
- 📝 **参数解析**:支持从用户输入中提取参数
---
## 🛠️ Command组件的基本结构
首先Command组件需要继承自`BaseCommand`类,并实现必要的方法。
```python
class ExampleCommand(BaseCommand):
command_name = "example" # 命令名称,作为唯一标识符
command_description = "这是一个示例命令" # 命令描述
command_pattern = r"" # 命令匹配的正则表达式
async def execute(self) -> Tuple[bool, Optional[str], bool]:
"""
执行Command的主要逻辑
Returns:
Tuple[bool, str, bool]:
- 第一个bool表示是否成功执行
- 第二个str是执行结果消息
- 第三个bool表示是否需要阻止消息继续处理
"""
# ---- 执行命令的逻辑 ----
return True, "执行成功", False
```
**`command_pattern`**: 该Command匹配的正则表达式用于精确匹配用户输入。
请注意:如果希望能获取到命令中的参数,请在正则表达式中使用有命名的捕获组,例如`(?P<param_name>pattern)`
这样在匹配时,内部实现可以使用`re.match.groupdict()`方法获取到所有捕获组的参数,并以字典的形式存储在`self.matched_groups`中。
### 匹配样例
假设我们有一个命令`/example param1=value1 param2=value2`,对应的正则表达式可以是:
```python
class ExampleCommand(BaseCommand):
command_name = "example"
command_description = "这是一个示例命令"
command_pattern = r"/example (?P<param1>\w+) (?P<param2>\w+)"
async def execute(self) -> Tuple[bool, Optional[str], bool]:
# 获取匹配的参数
param1 = self.matched_groups.get("param1")
param2 = self.matched_groups.get("param2")
# 执行逻辑
return True, f"参数1: {param1}, 参数2: {param2}", False
```
---
## Command 内置方法说明
```python
class BaseCommand:
def get_config(self, key: str, default=None):
"""获取插件配置值,使用嵌套键访问"""
async def send_text(self, content: str, reply_to: str = "") -> bool:
"""发送回复消息"""
async def send_type(self, message_type: str, content: str, display_message: str = "", typing: bool = False, reply_to: str = "") -> bool:
"""发送指定类型的回复消息到当前聊天环境"""
async def send_command(self, command_name: str, args: Optional[dict] = None, display_message: str = "", storage_message: bool = True) -> bool:
"""发送命令消息"""
async def send_emoji(self, emoji_base64: str) -> bool:
"""发送表情包"""
async def send_image(self, image_base64: str) -> bool:
"""发送图片"""
```
具体参数与用法参见`BaseCommand`基类的定义。

View File

@@ -9,7 +9,7 @@
## 组件功能详解 ## 组件功能详解
- [🧱 Action组件详解](action-components.md) - 掌握最核心的Action组件 - [🧱 Action组件详解](action-components.md) - 掌握最核心的Action组件
- [💻 Command组件详解](command-components.md) - 学习直接响应命令的组件 - [💻 Command组件详解](PLUS_COMMAND_GUIDE.md) - 学习直接响应命令的组件
- [🔧 Tool组件详解](tool-components.md) - 了解如何扩展信息获取能力 - [🔧 Tool组件详解](tool-components.md) - 了解如何扩展信息获取能力
- [⚙️ 配置文件系统指南](configuration-guide.md) - 学会使用自动生成的插件配置文件 - [⚙️ 配置文件系统指南](configuration-guide.md) - 学会使用自动生成的插件配置文件
- [📄 Manifest系统指南](manifest-guide.md) - 了解插件元数据管理和配置架构 - [📄 Manifest系统指南](manifest-guide.md) - 了解插件元数据管理和配置架构

View File

@@ -2,7 +2,7 @@
## 📖 什么是工具 ## 📖 什么是工具
工具是MoFox_Bot的信息获取能力扩展组件。如果说Action组件功能五花八门可以拓展麦麦能做的事情那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。 工具是MoFox_Bot的信息获取能力扩展组件。如果说Action组件功能五花八门可以拓展麦麦能做的事情那么Tool就是在某个过程中拓宽了MoFox_Bot能够获得的信息量。
### 🎯 工具的特点 ### 🎯 工具的特点

View File

@@ -1,121 +0,0 @@
# “月层计划”系统架构设计文档
## 1. 系统概述与目标
本系统旨在为MoFox_Bot引入一个动态的、由大型语言模型LLM驱动的“月层计划”机制。其核心目标是取代静态、预设的任务模板转而利用LLM在程序启动时自动生成符合Bot人设的、具有时效性的月度计划。这些计划将被存储、管理并在构建每日日程时被动态抽取和使用从而极大地丰富日程内容的个性和多样性。
---
## 2. 核心设计原则
- **动态性与智能化:** 所有计划内容均由LLM实时生成确保其独特性和创造性。
- **人设一致性:** 计划的生成将严格围绕Bot的核心人设进行强化角色形象。
- **持久化与可管理:** 生成的计划将被存入专用数据库表,便于管理和追溯。
- **消耗性与随机性:** 计划在使用后有一定几率被消耗(删除),模拟真实世界中计划的完成与迭代。
---
## 3. 系统核心流程规划
本系统包含两大核心流程:**启动时的计划生成流程**和**日程构建时的计划使用流程**。
### 3.1 流程一:启动时计划生成
此流程在每次程序启动时触发,负责填充当月的计划池。
```mermaid
graph TD
A[程序启动] --> B{检查当月计划池};
B -- 计划数量低于阈值 --> C[构建LLM Prompt];
C -- prompt包含Bot人设、月份等信息 --> D[调用LLM服务];
D -- LLM返回多个计划文本 --> E[解析并格式化计划];
E -- 逐条处理 --> F[存入`monthly_plans`数据库表];
F --> G[完成启动任务];
B -- 计划数量充足 --> G;
```
### 3.2 流程二:日程构建时计划使用
此流程在构建每日日程的提示词Prompt时触发。
```mermaid
graph TD
H[构建日程Prompt] --> I{查询数据库};
I -- 读取当月未使用的计划 --> J[随机抽取N个计划];
J --> K[将计划文本嵌入日程Prompt];
K --> L{随机数判断};
L -- 概率命中 --> M[将已抽取的计划标记为删除];
M --> N[完成Prompt构建];
L -- 概率未命中 --> N;
```
---
## 4. 数据库模型设计
为支撑本系统,需要新增一个数据库表。
**表名:** `monthly_plans`
| 字段名 | 类型 | 描述 |
| :--- | :--- | :--- |
| `id` | Integer | 主键,自增。 |
| `plan_text` | Text | 由LLM生成的计划内容原文。 |
| `target_month` | String(7) | 计划所属的月份,格式为 "YYYY-MM"。 |
| `is_deleted` | Boolean | 软删除标记,默认为 `false`。 |
| `created_at` | DateTime | 记录创建时间。 |
---
## 5. 详细模块规划
### 5.1 LLM Prompt生成模块
- **职责:** 构建高质量的Prompt以引导LLM生成符合要求的计划。
- **输入:** Bot人设描述、当前月份、期望生成的计划数量。
- **输出:** 一个结构化的Prompt字符串。
- **Prompt示例:**
```
你是一个[此处填入Bot人设描述例如活泼开朗、偶尔有些小迷糊的虚拟助手]。
请为即将到来的[YYYY年MM月]设计[N]个符合你身份的月度计划或目标。
要求:
1. 每个计划都是独立的、积极向上的。
2. 语言风格要自然、口语化,符合你的性格。
3. 每个计划用一句话或两句话简短描述。
4. 以JSON格式返回格式为{"plans": ["计划一", "计划二", ...]}
```
### 5.2 数据库交互模块
- **职责:** 提供对 `monthly_plans` 表的增、删、改、查接口。
- **规划函数列表:**
- `add_new_plans(plans: list[str], month: str)`: 批量添加新生成的计划。
- `get_active_plans_for_month(month: str) -> list`: 获取指定月份所有未被删除的计划。
- `soft_delete_plans(plan_ids: list[int])`: 将指定ID的计划标记为软删除。
### 5.3 配置项规划
需要在主配置文件 `config/bot_config.toml` 中添加以下配置项,以控制系统行为。
```toml
# ----------------------------------------------------------------
# 月层计划系统设置 (Monthly Plan System Settings)
# ----------------------------------------------------------------
[monthly_plan_system]
# 是否启用本功能
enable = true
# 启动时如果当月计划少于此数量则触发LLM生成
generation_threshold = 10
# 每次调用LLM期望生成的计划数量
plans_per_generation = 5
# 计划被使用后,被删除的概率 (0.0 到 1.0)
deletion_probability_on_use = 0.5
```
---
**文档结束。** 本文档纯粹为架构规划,旨在提供清晰的设计思路和开发指引,不包含任何实现代码。

View File

@@ -10,7 +10,6 @@ from src.chat.express.expression_learner import expression_learner_manager
from src.plugin_system.base.component_types import ChatMode from src.plugin_system.base.component_types import ChatMode
from src.schedule.schedule_manager import schedule_manager from src.schedule.schedule_manager import schedule_manager
from src.plugin_system.apis import message_api from src.plugin_system.apis import message_api
from src.mood.mood_manager import mood_manager
from .hfc_context import HfcContext from .hfc_context import HfcContext
from .energy_manager import EnergyManager from .energy_manager import EnergyManager

View File

@@ -4,7 +4,7 @@ import hashlib
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
import faiss import faiss
from typing import Any, Dict, Optional, Union, List from typing import Any, Dict, Optional, Union
from src.common.logger import get_logger from src.common.logger import get_logger
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config, model_config from src.config.config import global_config, model_config
@@ -14,6 +14,7 @@ from src.common.vector_db import vector_db_service
logger = get_logger("cache_manager") logger = get_logger("cache_manager")
class CacheManager: class CacheManager:
""" """
一个支持分层和语义缓存的通用工具缓存管理器。 一个支持分层和语义缓存的通用工具缓存管理器。
@@ -21,6 +22,7 @@ class CacheManager:
L1缓存: 内存字典 (KV) + FAISS (Vector)。 L1缓存: 内存字典 (KV) + FAISS (Vector)。
L2缓存: 数据库 (KV) + ChromaDB (Vector)。 L2缓存: 数据库 (KV) + ChromaDB (Vector)。
""" """
_instance = None _instance = None
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
@@ -32,7 +34,7 @@ class CacheManager:
""" """
初始化缓存管理器。 初始化缓存管理器。
""" """
if not hasattr(self, '_initialized'): if not hasattr(self, "_initialized"):
self.default_ttl = default_ttl self.default_ttl = default_ttl
self.semantic_cache_collection_name = "semantic_cache" self.semantic_cache_collection_name = "semantic_cache"
@@ -79,7 +81,7 @@ class CacheManager:
logger.warning("嵌入向量包含无效的数值 (NaN 或 Inf)") logger.warning("嵌入向量包含无效的数值 (NaN 或 Inf)")
return None return None
return embedding_array.astype('float32') return embedding_array.astype("float32")
else: else:
logger.warning(f"嵌入结果格式不支持: {type(embedding_result)}") logger.warning(f"嵌入结果格式不支持: {type(embedding_result)}")
return None return None
@@ -104,12 +106,18 @@ class CacheManager:
logger.warning(f"无法获取文件信息: {tool_file_path},错误: {e}") logger.warning(f"无法获取文件信息: {tool_file_path},错误: {e}")
try: try:
sorted_args = orjson.dumps(function_args, option=orjson.OPT_SORT_KEYS).decode('utf-8') sorted_args = orjson.dumps(function_args, option=orjson.OPT_SORT_KEYS).decode("utf-8")
except TypeError: except TypeError:
sorted_args = repr(sorted(function_args.items())) sorted_args = repr(sorted(function_args.items()))
return f"{tool_name}::{sorted_args}::{file_hash}" return f"{tool_name}::{sorted_args}::{file_hash}"
async def get(self, tool_name: str, function_args: Dict[str, Any], tool_file_path: Union[str, Path], semantic_query: Optional[str] = None) -> Optional[Any]: async def get(
self,
tool_name: str,
function_args: Dict[str, Any],
tool_file_path: Union[str, Path],
semantic_query: Optional[str] = None,
) -> Optional[Any]:
""" """
从缓存获取结果,查询顺序: L1-KV -> L1-Vector -> L2-KV -> L2-Vector。 从缓存获取结果,查询顺序: L1-KV -> L1-Vector -> L2-KV -> L2-Vector。
""" """
@@ -136,7 +144,7 @@ class CacheManager:
embedding_vector = embedding_result[0] if isinstance(embedding_result, tuple) else embedding_result embedding_vector = embedding_result[0] if isinstance(embedding_result, tuple) else embedding_result
validated_embedding = self._validate_embedding(embedding_vector) validated_embedding = self._validate_embedding(embedding_vector)
if validated_embedding is not None: if validated_embedding is not None:
query_embedding = np.array([validated_embedding], dtype='float32') query_embedding = np.array([validated_embedding], dtype="float32")
# 步骤 2a: L1 语义缓存 (FAISS) # 步骤 2a: L1 语义缓存 (FAISS)
if query_embedding is not None and self.l1_vector_index.ntotal > 0: if query_embedding is not None and self.l1_vector_index.ntotal > 0:
@@ -151,10 +159,7 @@ class CacheManager:
# 步骤 2b: L2 精确缓存 (数据库) # 步骤 2b: L2 精确缓存 (数据库)
cache_results_obj = await db_query( cache_results_obj = await db_query(
model_class=CacheEntries, model_class=CacheEntries, query_type="get", filters={"cache_key": key}, single_result=True
query_type="get",
filters={"cache_key": key},
single_result=True
) )
if cache_results_obj: if cache_results_obj:
@@ -172,8 +177,8 @@ class CacheManager:
filters={"cache_key": key}, filters={"cache_key": key},
data={ data={
"last_accessed": time.time(), "last_accessed": time.time(),
"access_count": getattr(cache_results_obj, "access_count", 0) + 1 "access_count": getattr(cache_results_obj, "access_count", 0) + 1,
} },
) )
# 回填 L1 # 回填 L1
@@ -181,11 +186,7 @@ class CacheManager:
return data return data
else: else:
# 删除过期的缓存条目 # 删除过期的缓存条目
await db_query( await db_query(model_class=CacheEntries, query_type="delete", filters={"cache_key": key})
model_class=CacheEntries,
query_type="delete",
filters={"cache_key": key}
)
# 步骤 2c: L2 语义缓存 (VectorDB Service) # 步骤 2c: L2 语义缓存 (VectorDB Service)
if query_embedding is not None: if query_embedding is not None:
@@ -193,14 +194,16 @@ class CacheManager:
results = vector_db_service.query( results = vector_db_service.query(
collection_name=self.semantic_cache_collection_name, collection_name=self.semantic_cache_collection_name,
query_embeddings=query_embedding.tolist(), query_embeddings=query_embedding.tolist(),
n_results=1 n_results=1,
)
if results and results.get("ids") and results["ids"][0]:
distance = (
results["distances"][0][0] if results.get("distances") and results["distances"][0] else "N/A"
) )
if results and results.get('ids') and results['ids'][0]:
distance = results['distances'][0][0] if results.get('distances') and results['distances'][0] else 'N/A'
logger.debug(f"L2语义搜索找到最相似的结果: id={results['ids'][0]}, 距离={distance}") logger.debug(f"L2语义搜索找到最相似的结果: id={results['ids'][0]}, 距离={distance}")
if distance != 'N/A' and distance < 0.75: if distance != "N/A" and distance < 0.75:
l2_hit_key = results['ids'][0][0] if isinstance(results['ids'][0], list) else results['ids'][0] l2_hit_key = results["ids"][0][0] if isinstance(results["ids"][0], list) else results["ids"][0]
logger.info(f"命中L2语义缓存: key='{l2_hit_key}', 距离={distance:.4f}") logger.info(f"命中L2语义缓存: key='{l2_hit_key}', 距离={distance:.4f}")
# 从数据库获取缓存数据 # 从数据库获取缓存数据
@@ -208,7 +211,7 @@ class CacheManager:
model_class=CacheEntries, model_class=CacheEntries,
query_type="get", query_type="get",
filters={"cache_key": l2_hit_key}, filters={"cache_key": l2_hit_key},
single_result=True single_result=True,
) )
if semantic_cache_results_obj: if semantic_cache_results_obj:
@@ -235,7 +238,15 @@ class CacheManager:
logger.debug(f"缓存未命中: {key}") logger.debug(f"缓存未命中: {key}")
return None return None
async def set(self, tool_name: str, function_args: Dict[str, Any], tool_file_path: Union[str, Path], data: Any, ttl: Optional[int] = None, semantic_query: Optional[str] = None): async def set(
self,
tool_name: str,
function_args: Dict[str, Any],
tool_file_path: Union[str, Path],
data: Any,
ttl: Optional[int] = None,
semantic_query: Optional[str] = None,
):
"""将结果存入所有缓存层。""" """将结果存入所有缓存层。"""
if ttl is None: if ttl is None:
ttl = self.default_ttl ttl = self.default_ttl
@@ -251,20 +262,15 @@ class CacheManager:
# 写入 L2 (数据库) # 写入 L2 (数据库)
cache_data = { cache_data = {
"cache_key": key, "cache_key": key,
"cache_value": orjson.dumps(data).decode('utf-8'), "cache_value": orjson.dumps(data).decode("utf-8"),
"expires_at": expires_at, "expires_at": expires_at,
"tool_name": tool_name, "tool_name": tool_name,
"created_at": time.time(), "created_at": time.time(),
"last_accessed": time.time(), "last_accessed": time.time(),
"access_count": 1 "access_count": 1,
} }
await db_save( await db_save(model_class=CacheEntries, data=cache_data, key_field="cache_key", key_value=key)
model_class=CacheEntries,
data=cache_data,
key_field="cache_key",
key_value=key
)
# 写入语义缓存 # 写入语义缓存
if semantic_query and self.embedding_model: if semantic_query and self.embedding_model:
@@ -274,7 +280,7 @@ class CacheManager:
embedding_vector = embedding_result[0] if isinstance(embedding_result, tuple) else embedding_result embedding_vector = embedding_result[0] if isinstance(embedding_result, tuple) else embedding_result
validated_embedding = self._validate_embedding(embedding_vector) validated_embedding = self._validate_embedding(embedding_vector)
if validated_embedding is not None: if validated_embedding is not None:
embedding = np.array([validated_embedding], dtype='float32') embedding = np.array([validated_embedding], dtype="float32")
# 写入 L1 Vector # 写入 L1 Vector
new_id = self.l1_vector_index.ntotal new_id = self.l1_vector_index.ntotal
@@ -286,7 +292,7 @@ class CacheManager:
vector_db_service.add( vector_db_service.add(
collection_name=self.semantic_cache_collection_name, collection_name=self.semantic_cache_collection_name,
embeddings=embedding.tolist(), embeddings=embedding.tolist(),
ids=[key] ids=[key],
) )
except Exception as e: except Exception as e:
logger.warning(f"语义缓存写入失败: {e}") logger.warning(f"语义缓存写入失败: {e}")
@@ -306,7 +312,7 @@ class CacheManager:
await db_query( await db_query(
model_class=CacheEntries, model_class=CacheEntries,
query_type="delete", query_type="delete",
filters={} # 删除所有记录 filters={}, # 删除所有记录
) )
# 清空 VectorDB # 清空 VectorDB
@@ -338,74 +344,12 @@ class CacheManager:
del self.l1_kv_cache[key] del self.l1_kv_cache[key]
# 清理L2过期条目 # 清理L2过期条目
await db_query( await db_query(model_class=CacheEntries, query_type="delete", filters={"expires_at": {"$lt": current_time}})
model_class=CacheEntries,
query_type="delete",
filters={"expires_at": {"$lt": current_time}}
)
if expired_keys: if expired_keys:
logger.info(f"清理了 {len(expired_keys)} 个过期的L1缓存条目") logger.info(f"清理了 {len(expired_keys)} 个过期的L1缓存条目")
# 全局实例 # 全局实例
tool_cache = CacheManager() tool_cache = CacheManager()
import inspect
import time
def wrap_tool_executor():
"""
包装工具执行器以添加缓存功能
这个函数应该在系统启动时被调用一次
"""
from src.plugin_system.core.tool_use import ToolExecutor
from src.plugin_system.apis.tool_api import get_tool_instance
original_execute = ToolExecutor.execute_tool_call
async def wrapped_execute_tool_call(self, tool_call, tool_instance=None):
if not tool_instance:
tool_instance = get_tool_instance(tool_call.func_name)
if not tool_instance or not tool_instance.enable_cache:
return await original_execute(self, tool_call, tool_instance)
try:
tool_file_path = inspect.getfile(tool_instance.__class__)
semantic_query = None
if tool_instance.semantic_cache_query_key:
semantic_query = tool_call.args.get(tool_instance.semantic_cache_query_key)
cached_result = await tool_cache.get(
tool_name=tool_call.func_name,
function_args=tool_call.args,
tool_file_path=tool_file_path,
semantic_query=semantic_query
)
if cached_result:
logger.info(f"{getattr(self, 'log_prefix', '')}使用缓存结果,跳过工具 {tool_call.func_name} 执行")
return cached_result
except Exception as e:
logger.error(f"{getattr(self, 'log_prefix', '')}检查工具缓存时出错: {e}")
result = await original_execute(self, tool_call, tool_instance)
try:
tool_file_path = inspect.getfile(tool_instance.__class__)
semantic_query = None
if tool_instance.semantic_cache_query_key:
semantic_query = tool_call.args.get(tool_instance.semantic_cache_query_key)
await tool_cache.set(
tool_name=tool_call.func_name,
function_args=tool_call.args,
tool_file_path=tool_file_path,
data=result,
ttl=tool_instance.cache_ttl,
semantic_query=semantic_query
)
except Exception as e:
logger.error(f"{getattr(self, 'log_prefix', '')}设置工具缓存时出错: {e}")
return result
ToolExecutor.execute_tool_call = wrapped_execute_tool_call

View File

@@ -311,6 +311,12 @@ class LLMRequest:
messages = [message_builder.build()] messages = [message_builder.build()]
tool_built = self._build_tool_options(tools) tool_built = self._build_tool_options(tools)
# 针对当前模型的空回复/截断重试逻辑
empty_retry_count = 0
max_empty_retry = api_provider.max_retry
empty_retry_interval = api_provider.retry_interval
while empty_retry_count <= max_empty_retry:
response = await self._execute_request( response = await self._execute_request(
api_provider=api_provider, api_provider=api_provider,
client=client, client=client,
@@ -339,14 +345,18 @@ class LLMRequest:
is_truncated = True is_truncated = True
if is_empty_reply or is_truncated: if is_empty_reply or is_truncated:
# 空回复或截断不进行模型切换,仅记录错误后抛出或返回 empty_retry_count += 1
if empty_retry_count <= max_empty_retry:
reason = "空回复" if is_empty_reply else "截断" reason = "空回复" if is_empty_reply else "截断"
msg = f"模型 '{model_name}' 生成了{reason}的回复" logger.warning(f"模型 '{model_name}' 检测到{reason},正在进行第 {empty_retry_count}/{max_empty_retry} 次重新生成...")
logger.error(msg) if empty_retry_interval > 0:
if raise_when_empty: await asyncio.sleep(empty_retry_interval)
raise RuntimeError(msg) continue # 继续使用当前模型重试
return msg, (reasoning_content, model_name, tool_calls) else:
# 当前模型重试次数用尽,跳出内层循环,触发外层循环切换模型
reason = "空回复" if is_empty_reply else "截断"
logger.error(f"模型 '{model_name}' 经过 {max_empty_retry} 次重试后仍然是{reason}的回复。")
raise RuntimeError(f"模型 '{model_name}' 达到最大空回复/截断重试次数")
# 成功获取响应 # 成功获取响应
if usage := response.usage: if usage := response.usage:
@@ -368,24 +378,26 @@ class LLMRequest:
logger.error(f"模型 '{model_name}' 遇到认证/权限错误 (Code: {e.status_code}),将尝试下一个模型。") logger.error(f"模型 '{model_name}' 遇到认证/权限错误 (Code: {e.status_code}),将尝试下一个模型。")
failed_models.add(model_name) failed_models.add(model_name)
last_exception = e last_exception = e
continue continue # 切换到下一个模型
else: else:
# 对于其他HTTP错误不切换模型直接抛出
logger.error(f"模型 '{model_name}' 请求失败HTTP状态码: {e.status_code}") logger.error(f"模型 '{model_name}' 请求失败HTTP状态码: {e.status_code}")
last_exception = e
if raise_when_empty: if raise_when_empty:
raise raise
break # 对于其他HTTP错误直接抛出不再尝试其他模型
return f"请求失败: {e}", ("", model_name, None)
except RuntimeError as e: except RuntimeError as e:
# 捕获所有重试失败(包括空回复和网络问题)
logger.error(f"模型 '{model_name}' 在所有重试后仍然失败: {e},将尝试下一个模型。") logger.error(f"模型 '{model_name}' 在所有重试后仍然失败: {e},将尝试下一个模型。")
failed_models.add(model_name) failed_models.add(model_name)
last_exception = e last_exception = e
continue continue # 切换到下一个模型
except Exception as e: except Exception as e:
logger.error(f"使用模型 '{model_name}' 时发生未知异常: {e}") logger.error(f"使用模型 '{model_name}' 时发生未知异常: {e}")
failed_models.add(model_name) failed_models.add(model_name)
last_exception = e last_exception = e
continue continue # 切换到下一个模型
# 所有模型都尝试失败 # 所有模型都尝试失败
logger.error("所有可用模型都已尝试失败。") logger.error("所有可用模型都已尝试失败。")

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, Type from typing import Optional, Type
from src.plugin_system.base.base_tool import BaseTool from src.plugin_system.base.base_tool import BaseTool
from src.plugin_system.base.component_types import ComponentType from src.plugin_system.base.component_types import ComponentType

View File

@@ -1,5 +1,5 @@
from abc import abstractmethod from abc import abstractmethod
from typing import List, Type, Tuple, Union, TYPE_CHECKING from typing import List, Type, Tuple, Union
from .plugin_base import PluginBase from .plugin_base import PluginBase
from src.common.logger import get_logger from src.common.logger import get_logger

View File

@@ -4,7 +4,7 @@
""" """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Dict, Tuple, Optional, List from typing import Tuple, Optional, List
import re import re
from src.common.logger import get_logger from src.common.logger import get_logger

View File

@@ -7,8 +7,10 @@ from src.llm_models.utils_model import LLMRequest
from src.llm_models.payload_content import ToolCall from src.llm_models.payload_content import ToolCall
from src.config.config import global_config, model_config from src.config.config import global_config, model_config
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
import inspect
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from src.common.logger import get_logger from src.common.logger import get_logger
from src.common.cache_manager import tool_cache
logger = get_logger("tool_use") logger = get_logger("tool_use")
@@ -184,28 +186,71 @@ class ToolExecutor:
return tool_results, used_tools return tool_results, used_tools
async def execute_tool_call(self, tool_call: ToolCall, tool_instance: Optional[BaseTool] = None) -> Optional[Dict[str, Any]]: async def execute_tool_call(self, tool_call: ToolCall, tool_instance: Optional[BaseTool] = None) -> Optional[Dict[str, Any]]:
# sourcery skip: use-assigned-variable """执行单个工具调用,并处理缓存"""
"""执行单个工具调用
Args: function_args = tool_call.args or {}
tool_call: 工具调用对象 tool_instance = tool_instance or get_tool_instance(tool_call.func_name)
Returns: # 如果工具不存在或未启用缓存,则直接执行
Optional[Dict]: 工具调用结果如果失败则返回None if not tool_instance or not tool_instance.enable_cache:
""" return await self._original_execute_tool_call(tool_call, tool_instance)
# --- 缓存逻辑开始 ---
try:
tool_file_path = inspect.getfile(tool_instance.__class__)
semantic_query = None
if tool_instance.semantic_cache_query_key:
semantic_query = function_args.get(tool_instance.semantic_cache_query_key)
cached_result = await tool_cache.get(
tool_name=tool_call.func_name,
function_args=function_args,
tool_file_path=tool_file_path,
semantic_query=semantic_query
)
if cached_result:
logger.info(f"{self.log_prefix}使用缓存结果,跳过工具 {tool_call.func_name} 执行")
return cached_result
except Exception as e:
logger.error(f"{self.log_prefix}检查工具缓存时出错: {e}")
# 缓存未命中,执行原始工具调用
result = await self._original_execute_tool_call(tool_call, tool_instance)
# 将结果存入缓存
try:
tool_file_path = inspect.getfile(tool_instance.__class__)
semantic_query = None
if tool_instance.semantic_cache_query_key:
semantic_query = function_args.get(tool_instance.semantic_cache_query_key)
await tool_cache.set(
tool_name=tool_call.func_name,
function_args=function_args,
tool_file_path=tool_file_path,
data=result,
ttl=tool_instance.cache_ttl,
semantic_query=semantic_query
)
except Exception as e:
logger.error(f"{self.log_prefix}设置工具缓存时出错: {e}")
# --- 缓存逻辑结束 ---
return result
async def _original_execute_tool_call(self, tool_call: ToolCall, tool_instance: Optional[BaseTool] = None) -> Optional[Dict[str, Any]]:
"""执行单个工具调用的原始逻辑"""
try: try:
function_name = tool_call.func_name function_name = tool_call.func_name
function_args = tool_call.args or {} function_args = tool_call.args or {}
logger.info(f"🤖 {self.log_prefix} 正在执行工具: [bold green]{function_name}[/bold green] | 参数: {function_args}") logger.info(f"{self.log_prefix} 正在执行工具: [bold green]{function_name}[/bold green] | 参数: {function_args}")
function_args["llm_called"] = True # 标记为LLM调用 function_args["llm_called"] = True
# 获取对应工具实例
tool_instance = tool_instance or get_tool_instance(function_name) tool_instance = tool_instance or get_tool_instance(function_name)
if not tool_instance: if not tool_instance:
logger.warning(f"未知工具名称: {function_name}") logger.warning(f"未知工具名称: {function_name}")
return None return None
# 执行工具并记录日志
logger.debug(f"{self.log_prefix}执行工具 {function_name},参数: {function_args}") logger.debug(f"{self.log_prefix}执行工具 {function_name},参数: {function_args}")
result = await tool_instance.execute(function_args) result = await tool_instance.execute(function_args)
if result: if result:

View File

@@ -9,12 +9,9 @@ import datetime
import base64 import base64
import aiohttp import aiohttp
from src.common.logger import get_logger from src.common.logger import get_logger
import base64
import aiohttp
import imghdr import imghdr
import asyncio import asyncio
from src.common.logger import get_logger from src.plugin_system.apis import llm_api, config_api, generator_api
from src.plugin_system.apis import llm_api, config_api, generator_api, person_api
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from maim_message import UserInfo from maim_message import UserInfo
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest

View File

@@ -16,7 +16,7 @@ from src.plugin_system.apis.permission_api import permission_api
from src.plugin_system.apis.logging_api import get_logger from src.plugin_system.apis.logging_api import get_logger
from src.plugin_system.base.component_types import PlusCommandInfo, ChatType from src.plugin_system.base.component_types import PlusCommandInfo, ChatType
from src.plugin_system.base.config_types import ConfigField from src.plugin_system.base.config_types import ConfigField
from src.plugin_system.utils.permission_decorators import require_permission, require_master, PermissionChecker from src.plugin_system.utils.permission_decorators import require_permission
logger = get_logger("Permission") logger = get_logger("Permission")

View File

@@ -411,7 +411,6 @@ class ScheduleManager:
通过关键词匹配、唤醒度、睡眠压力等综合判断是否处于休眠时间。 通过关键词匹配、唤醒度、睡眠压力等综合判断是否处于休眠时间。
新增弹性睡眠机制,允许在压力低时延迟入睡,并在入睡前发送通知。 新增弹性睡眠机制,允许在压力低时延迟入睡,并在入睡前发送通知。
""" """
from src.chat.chat_loop.wakeup_manager import WakeUpManager
# --- 基础检查 --- # --- 基础检查 ---
if not global_config.schedule.enable_is_sleep: if not global_config.schedule.enable_is_sleep:
return False return False