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:
@@ -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`基类的定义。
|
|
||||||
@@ -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) - 了解插件元数据管理和配置架构
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 📖 什么是工具
|
## 📖 什么是工具
|
||||||
|
|
||||||
工具是MoFox_Bot的信息获取能力扩展组件。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。
|
工具是MoFox_Bot的信息获取能力扩展组件。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了MoFox_Bot能够获得的信息量。
|
||||||
|
|
||||||
### 🎯 工具的特点
|
### 🎯 工具的特点
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
**文档结束。** 本文档纯粹为架构规划,旨在提供清晰的设计思路和开发指引,不包含任何实现代码。
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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("所有可用模型都已尝试失败。")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user