Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
@@ -155,88 +155,22 @@ class ChatterPlanFilter:
|
||||
identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:"
|
||||
|
||||
schedule_block = ""
|
||||
# 优先检查是否被吵醒
|
||||
from src.chat.message_manager.message_manager import message_manager
|
||||
|
||||
angry_prompt_addition = ""
|
||||
try:
|
||||
from src.plugins.built_in.sleep_system.api import get_wakeup_manager
|
||||
wakeup_mgr = get_wakeup_manager()
|
||||
except ImportError:
|
||||
logger.debug("无法导入睡眠系统API,将跳过相关检查。")
|
||||
wakeup_mgr = None
|
||||
|
||||
if wakeup_mgr:
|
||||
|
||||
# 双重检查确保愤怒状态不会丢失
|
||||
# 检查1: 直接从 wakeup_manager 获取
|
||||
if wakeup_mgr.is_in_angry_state():
|
||||
angry_prompt_addition = wakeup_mgr.get_angry_prompt_addition()
|
||||
|
||||
# 检查2: 如果上面没获取到,再从 mood_manager 确认
|
||||
if not angry_prompt_addition:
|
||||
chat_mood_for_check = mood_manager.get_mood_by_chat_id(plan.chat_id)
|
||||
if chat_mood_for_check.is_angry_from_wakeup:
|
||||
angry_prompt_addition = global_config.sleep_system.angry_prompt
|
||||
|
||||
if angry_prompt_addition:
|
||||
schedule_block = angry_prompt_addition
|
||||
elif global_config.planning_system.schedule_enable:
|
||||
if global_config.planning_system.schedule_enable:
|
||||
if activity_info := schedule_manager.get_current_activity():
|
||||
activity = activity_info.get("activity", "未知活动")
|
||||
schedule_block = f"你当前正在:{activity},但注意它与群聊的聊天无关。"
|
||||
|
||||
mood_block = ""
|
||||
# 如果被吵醒,则心情也是愤怒的,不需要另外的情绪模块
|
||||
if not angry_prompt_addition and global_config.mood.enable_mood:
|
||||
# 需要情绪模块打开才能获得情绪,否则会引发报错
|
||||
if global_config.mood.enable_mood:
|
||||
chat_mood = mood_manager.get_mood_by_chat_id(plan.chat_id)
|
||||
mood_block = f"你现在的心情是:{chat_mood.mood_state}"
|
||||
|
||||
if plan.mode == ChatMode.PROACTIVE:
|
||||
long_term_memory_block = await self._get_long_term_memory_context()
|
||||
|
||||
chat_content_block, message_id_list = await build_readable_messages_with_id(
|
||||
messages=[msg.flatten() for msg in plan.chat_history],
|
||||
timestamp_mode="normal",
|
||||
truncate=False,
|
||||
show_actions=False,
|
||||
)
|
||||
|
||||
prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt")
|
||||
actions_before_now = await get_actions_by_timestamp_with_chat(
|
||||
chat_id=plan.chat_id,
|
||||
timestamp_start=time.time() - 3600,
|
||||
timestamp_end=time.time(),
|
||||
limit=5,
|
||||
)
|
||||
actions_before_now_block = build_readable_actions(actions=actions_before_now)
|
||||
actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
||||
|
||||
prompt = prompt_template.format(
|
||||
time_block=time_block,
|
||||
identity_block=identity_block,
|
||||
schedule_block=schedule_block,
|
||||
mood_block=mood_block,
|
||||
long_term_memory_block=long_term_memory_block,
|
||||
chat_content_block=chat_content_block or "最近没有聊天内容。",
|
||||
actions_before_now_block=actions_before_now_block,
|
||||
)
|
||||
return prompt, message_id_list
|
||||
|
||||
# 构建已读/未读历史消息
|
||||
read_history_block, unread_history_block, message_id_list = await self._build_read_unread_history_blocks(
|
||||
plan
|
||||
)
|
||||
|
||||
# 为了兼容性,保留原有的chat_content_block
|
||||
chat_content_block, _ = await build_readable_messages_with_id(
|
||||
messages=[msg.flatten() for msg in plan.chat_history],
|
||||
timestamp_mode="normal",
|
||||
read_mark=self.last_obs_time_mark,
|
||||
truncate=True,
|
||||
show_actions=True,
|
||||
)
|
||||
|
||||
actions_before_now = await get_actions_by_timestamp_with_chat(
|
||||
chat_id=plan.chat_id,
|
||||
timestamp_start=time.time() - 3600,
|
||||
@@ -286,7 +220,7 @@ class ChatterPlanFilter:
|
||||
is_group_chat = plan.chat_type == ChatType.GROUP
|
||||
chat_context_description = "你现在正在一个群聊中"
|
||||
if not is_group_chat and plan.target_info:
|
||||
chat_target_name = plan.target_info.get("person_name") or plan.target_info.get("user_nickname") or "对方"
|
||||
chat_target_name = plan.target_info.person_name or plan.target_info.user_nickname or "对方"
|
||||
chat_context_description = f"你正在和 {chat_target_name} 私聊"
|
||||
|
||||
action_options_block = await self._build_action_options(plan.available_actions)
|
||||
|
||||
@@ -9,7 +9,7 @@ from src.chat.utils.utils import get_chat_type_and_target_info
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.common.data_models.info_data_model import Plan, TargetPersonInfo
|
||||
from src.config.config import global_config
|
||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode, ChatType
|
||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode, ChatType, ComponentType
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ class ChatterPlanGenerator:
|
||||
try:
|
||||
# 获取聊天类型和目标信息
|
||||
chat_type, target_info = await get_chat_type_and_target_info(self.chat_id)
|
||||
if chat_type:
|
||||
chat_type = ChatType.GROUP
|
||||
else:
|
||||
#遇到未知类型也当私聊处理
|
||||
chat_type = ChatType.PRIVATE
|
||||
|
||||
# 获取可用动作列表
|
||||
available_actions = await self._get_available_actions(chat_type, mode)
|
||||
@@ -62,12 +67,16 @@ class ChatterPlanGenerator:
|
||||
# 获取聊天历史记录
|
||||
recent_messages = await self._get_recent_messages()
|
||||
|
||||
# 构建计划对象
|
||||
# 使用 target_info 字典创建 TargetPersonInfo 实例
|
||||
target_person_info = TargetPersonInfo(**target_info) if target_info else TargetPersonInfo()
|
||||
|
||||
# 构建计划对象
|
||||
plan = Plan(
|
||||
chat_id=self.chat_id,
|
||||
chat_type=chat_type,
|
||||
mode=mode,
|
||||
target_info=target_info,
|
||||
target_info=target_person_info,
|
||||
available_actions=available_actions,
|
||||
chat_history=recent_messages,
|
||||
)
|
||||
@@ -77,6 +86,7 @@ class ChatterPlanGenerator:
|
||||
except Exception:
|
||||
# 如果生成失败,返回一个基本的空计划
|
||||
return Plan(
|
||||
chat_type = ChatType.PRIVATE,#空计划默认当成私聊
|
||||
chat_id=self.chat_id,
|
||||
mode=mode,
|
||||
target_info=TargetPersonInfo(),
|
||||
@@ -124,7 +134,7 @@ class ChatterPlanGenerator:
|
||||
try:
|
||||
# 获取最近的消息记录
|
||||
raw_messages = await get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=self.chat_id, timestamp=time.time(), limit=global_config.memory.short_memory_length
|
||||
chat_id=self.chat_id, timestamp=time.time(), limit=global_config.chat.max_context_size
|
||||
)
|
||||
|
||||
# 转换为 DatabaseMessages 对象
|
||||
|
||||
@@ -70,6 +70,7 @@ class ChatterActionPlanner:
|
||||
"replies_generated": 0,
|
||||
"other_actions_executed": 0,
|
||||
}
|
||||
self._background_tasks: set[asyncio.Task] = set()
|
||||
|
||||
async def plan(self, context: "StreamContext | None" = None) -> tuple[list[dict[str, Any]], Any | None]:
|
||||
"""
|
||||
@@ -157,7 +158,9 @@ class ChatterActionPlanner:
|
||||
)
|
||||
|
||||
if interest_updates:
|
||||
asyncio.create_task(self._commit_interest_updates(interest_updates))
|
||||
task = asyncio.create_task(self._commit_interest_updates(interest_updates))
|
||||
self._background_tasks.add(task)
|
||||
task.add_done_callback(self._handle_task_result)
|
||||
|
||||
# 检查兴趣度是否达到非回复动作阈值
|
||||
non_reply_action_interest_threshold = global_config.affinity_flow.non_reply_action_interest_threshold
|
||||
@@ -266,6 +269,17 @@ class ChatterActionPlanner:
|
||||
|
||||
return final_actions_dict, final_target_message_dict
|
||||
|
||||
def _handle_task_result(self, task: asyncio.Task) -> None:
|
||||
"""处理后台任务的结果,记录异常。"""
|
||||
try:
|
||||
task.result()
|
||||
except asyncio.CancelledError:
|
||||
pass # 任务被取消是正常现象
|
||||
except Exception as e:
|
||||
logger.error(f"后台任务执行失败: {e}", exc_info=True)
|
||||
finally:
|
||||
self._background_tasks.discard(task)
|
||||
|
||||
def get_planner_stats(self) -> dict[str, Any]:
|
||||
"""获取规划器统计"""
|
||||
return self.planner_stats.copy()
|
||||
|
||||
@@ -15,7 +15,7 @@ logger = get_logger(__name__)
|
||||
|
||||
@register_plugin
|
||||
class ProactiveThinkerPlugin(BasePlugin):
|
||||
"""一个主动思考的插件,但现在还只是个空壳子"""
|
||||
"""一个主动思考的插件"""
|
||||
|
||||
plugin_name: str = "proactive_thinker"
|
||||
enable_plugin: bool = True
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime
|
||||
|
||||
from maim_message import UserInfo
|
||||
|
||||
from src.chat.message_manager.sleep_system.state_manager import SleepState, sleep_state_manager
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
@@ -13,7 +14,6 @@ from src.manager.async_task_manager import AsyncTask, async_task_manager
|
||||
from src.plugin_system import BaseEventHandler, EventType
|
||||
from src.plugin_system.apis import chat_api, message_api, person_api
|
||||
from src.plugin_system.base.base_event import HandlerResult
|
||||
from src.chat.message_manager.sleep_system.state_manager import SleepState, sleep_state_manager
|
||||
|
||||
from .proactive_thinker_executor import ProactiveThinkerExecutor
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Base search engine interface
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class BaseSearchEngine(ABC):
|
||||
@@ -24,6 +24,12 @@ class BaseSearchEngine(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
async def read_url(self, url: str) -> Optional[str]:
|
||||
"""
|
||||
读取URL内容,如果引擎不支持则返回None
|
||||
"""
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def is_available(self) -> bool:
|
||||
"""
|
||||
|
||||
107
src/plugins/built_in/web_search_tool/engines/metaso_engine.py
Normal file
107
src/plugins/built_in/web_search_tool/engines/metaso_engine.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Metaso Search Engine (Chat Completions Mode)
|
||||
"""
|
||||
import json
|
||||
from typing import Any, List
|
||||
|
||||
import httpx
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.apis import config_api
|
||||
|
||||
from ..utils.api_key_manager import create_api_key_manager_from_config
|
||||
from .base import BaseSearchEngine
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class MetasoClient:
|
||||
"""A client to interact with the Metaso API."""
|
||||
|
||||
def __init__(self, api_key: str):
|
||||
self.api_key = api_key
|
||||
self.base_url = "https://metaso.cn/api/v1"
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
async def search(self, query: str, **kwargs) -> List[dict[str, Any]]:
|
||||
"""Perform a search using the Metaso Chat Completions API."""
|
||||
payload = {"model": "fast", "stream": True, "messages": [{"role": "user", "content": query}]}
|
||||
search_url = f"{self.base_url}/chat/completions"
|
||||
full_response_content = ""
|
||||
|
||||
async with httpx.AsyncClient(timeout=90.0) as client:
|
||||
try:
|
||||
async with client.stream("POST", search_url, headers=self.headers, json=payload) as response:
|
||||
response.raise_for_status()
|
||||
async for line in response.aiter_lines():
|
||||
if line.startswith("data:"):
|
||||
data_str = line[len("data:") :].strip()
|
||||
if data_str == "[DONE]":
|
||||
break
|
||||
try:
|
||||
data = json.loads(data_str)
|
||||
delta = data.get("choices", [{}])[0].get("delta", {})
|
||||
content_chunk = delta.get("content")
|
||||
if content_chunk:
|
||||
full_response_content += content_chunk
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Metaso stream: could not decode JSON line: {data_str}")
|
||||
continue
|
||||
|
||||
if not full_response_content:
|
||||
logger.warning("Metaso search returned an empty stream.")
|
||||
return []
|
||||
|
||||
return [
|
||||
{
|
||||
"title": query,
|
||||
"url": "https://metaso.cn/",
|
||||
"snippet": full_response_content,
|
||||
"provider": "Metaso (Chat)",
|
||||
}
|
||||
]
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(f"HTTP error occurred while searching with Metaso Chat: {e.response.text}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while searching with Metaso Chat: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
class MetasoSearchEngine(BaseSearchEngine):
|
||||
"""Metaso Search Engine implementation."""
|
||||
|
||||
def __init__(self):
|
||||
self._initialize_clients()
|
||||
|
||||
def _initialize_clients(self):
|
||||
"""Initialize Metaso clients."""
|
||||
metaso_api_keys = config_api.get_global_config("web_search.metaso_api_keys", None)
|
||||
self.api_manager = create_api_key_manager_from_config(
|
||||
metaso_api_keys, lambda key: MetasoClient(api_key=key), "Metaso"
|
||||
)
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if the Metaso search engine is available."""
|
||||
return self.api_manager.is_available()
|
||||
|
||||
async def search(self, args: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Execute a Metaso search."""
|
||||
if not self.is_available():
|
||||
return []
|
||||
|
||||
query = args["query"]
|
||||
try:
|
||||
metaso_client = self.api_manager.get_next_client()
|
||||
if not metaso_client:
|
||||
logger.error("Could not get Metaso client.")
|
||||
return []
|
||||
|
||||
return await metaso_client.search(query)
|
||||
except Exception as e:
|
||||
logger.error(f"Metaso search failed: {e}", exc_info=True)
|
||||
return []
|
||||
@@ -22,6 +22,7 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
提供网络搜索和URL解析功能,支持多种搜索引擎:
|
||||
- Exa (需要API密钥)
|
||||
- Tavily (需要API密钥)
|
||||
- Metaso (需要API密钥)
|
||||
- DuckDuckGo (免费)
|
||||
- Bing (免费)
|
||||
"""
|
||||
@@ -43,6 +44,7 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
from .engines.exa_engine import ExaSearchEngine
|
||||
from .engines.searxng_engine import SearXNGSearchEngine
|
||||
from .engines.tavily_engine import TavilySearchEngine
|
||||
from .engines.metaso_engine import MetasoSearchEngine
|
||||
|
||||
# 实例化所有搜索引擎,这会触发API密钥管理器的初始化
|
||||
exa_engine = ExaSearchEngine()
|
||||
@@ -50,14 +52,16 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
||||
ddg_engine = DDGSearchEngine()
|
||||
bing_engine = BingSearchEngine()
|
||||
searxng_engine = SearXNGSearchEngine()
|
||||
|
||||
# 报告每个引擎的状态
|
||||
metaso_engine = MetasoSearchEngine()
|
||||
|
||||
# 报告每个引擎的状态
|
||||
engines_status = {
|
||||
"Exa": exa_engine.is_available(),
|
||||
"Tavily": tavily_engine.is_available(),
|
||||
"DuckDuckGo": ddg_engine.is_available(),
|
||||
"Bing": bing_engine.is_available(),
|
||||
"SearXNG": searxng_engine.is_available(),
|
||||
"Metaso": metaso_engine.is_available(),
|
||||
}
|
||||
|
||||
available_engines = [name for name, available in engines_status.items() if available]
|
||||
|
||||
@@ -15,6 +15,7 @@ from ..engines.ddg_engine import DDGSearchEngine
|
||||
from ..engines.exa_engine import ExaSearchEngine
|
||||
from ..engines.searxng_engine import SearXNGSearchEngine
|
||||
from ..engines.tavily_engine import TavilySearchEngine
|
||||
from ..engines.metaso_engine import MetasoSearchEngine
|
||||
from ..utils.formatters import deduplicate_results, format_search_results
|
||||
|
||||
logger = get_logger("web_search_tool")
|
||||
@@ -51,6 +52,7 @@ class WebSurfingTool(BaseTool):
|
||||
"ddg": DDGSearchEngine(),
|
||||
"bing": BingSearchEngine(),
|
||||
"searxng": SearXNGSearchEngine(),
|
||||
"metaso": MetasoSearchEngine(),
|
||||
}
|
||||
|
||||
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user