feat: 更新多个文件以使用 SessionMessage 替代 MaiMessage,并调整相关逻辑
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
"startup.main_error": "Main process encountered an exception: {error}",
|
"startup.main_error": "Main process encountered an exception: {error}",
|
||||||
"startup.opensource_free_notice": " This project is completely free and open-source software, released under the GPL-3.0 license",
|
"startup.opensource_free_notice": " This project is completely free and open-source software, released under the GPL-3.0 license",
|
||||||
"startup.opensource_group": " Official group chat: ",
|
"startup.opensource_group": " Official group chat: ",
|
||||||
"startup.opensource_group_value": "1006149251",
|
"startup.opensource_group_value": "766798517",
|
||||||
"startup.opensource_repo": " Official repository: ",
|
"startup.opensource_repo": " Official repository: ",
|
||||||
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
||||||
"startup.opensource_resale_warning": " Reselling this software as a \"product\" or concealing its open-source nature violates the license!",
|
"startup.opensource_resale_warning": " Reselling this software as a \"product\" or concealing its open-source nature violates the license!",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"startup.main_error": "メインプロセスで例外が発生しました: {error}",
|
"startup.main_error": "メインプロセスで例外が発生しました: {error}",
|
||||||
"startup.opensource_free_notice": " 本プロジェクトは完全無料のオープンソースソフトウェアであり、GPL-3.0 ライセンスのもとで公開されています",
|
"startup.opensource_free_notice": " 本プロジェクトは完全無料のオープンソースソフトウェアであり、GPL-3.0 ライセンスのもとで公開されています",
|
||||||
"startup.opensource_group": " 公式グループ: ",
|
"startup.opensource_group": " 公式グループ: ",
|
||||||
"startup.opensource_group_value": "1006149251",
|
"startup.opensource_group_value": "766798517",
|
||||||
"startup.opensource_repo": " 公式リポジトリ: ",
|
"startup.opensource_repo": " 公式リポジトリ: ",
|
||||||
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
||||||
"startup.opensource_resale_warning": " 本ソフトウェアを「商品」として転売したり、オープンソースであることを隠すことはライセンス違反です!",
|
"startup.opensource_resale_warning": " 本ソフトウェアを「商品」として転売したり、オープンソースであることを隠すことはライセンス違反です!",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"startup.main_error": "메인 프로세스에서 예외 발생: {error}",
|
"startup.main_error": "메인 프로세스에서 예외 발생: {error}",
|
||||||
"startup.opensource_free_notice": " 본 프로젝트는 완전 무료 오픈소스 소프트웨어이며, GPL-3.0 라이선스로 배포됩니다",
|
"startup.opensource_free_notice": " 본 프로젝트는 완전 무료 오픈소스 소프트웨어이며, GPL-3.0 라이선스로 배포됩니다",
|
||||||
"startup.opensource_group": " 공식 그룹: ",
|
"startup.opensource_group": " 공식 그룹: ",
|
||||||
"startup.opensource_group_value": "1006149251",
|
"startup.opensource_group_value": "766798517",
|
||||||
"startup.opensource_repo": " 공식 저장소: ",
|
"startup.opensource_repo": " 공식 저장소: ",
|
||||||
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
||||||
"startup.opensource_resale_warning": " 본 소프트웨어를 '상품'으로 재판매하거나 오픈소스임을 숨기는 행위는 라이선스 위반입니다!",
|
"startup.opensource_resale_warning": " 본 소프트웨어를 '상품'으로 재판매하거나 오픈소스임을 숨기는 행위는 라이선스 위반입니다!",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"startup.main_error": "主程序发生异常: {error}",
|
"startup.main_error": "主程序发生异常: {error}",
|
||||||
"startup.opensource_free_notice": " 本项目是完全免费的开源软件,基于 GPL-3.0 协议发布",
|
"startup.opensource_free_notice": " 本项目是完全免费的开源软件,基于 GPL-3.0 协议发布",
|
||||||
"startup.opensource_group": " 官方群聊: ",
|
"startup.opensource_group": " 官方群聊: ",
|
||||||
"startup.opensource_group_value": "1006149251",
|
"startup.opensource_group_value": "766798517",
|
||||||
"startup.opensource_repo": " 官方仓库: ",
|
"startup.opensource_repo": " 官方仓库: ",
|
||||||
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
|
||||||
"startup.opensource_resale_warning": " 将本软件作为「商品」倒卖、隐瞒开源性质均违反协议!",
|
"startup.opensource_resale_warning": " 将本软件作为「商品」倒卖、隐瞒开源性质均违反协议!",
|
||||||
|
|||||||
55
pytests/test_maisaka_message_adapter.py
Normal file
55
pytests/test_maisaka_message_adapter.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from src.chat.message_receive.message import SessionMessage
|
||||||
|
from src.common.data_models.message_component_data_model import MessageSequence, TextComponent
|
||||||
|
from src.llm_models.payload_content.tool_option import ToolCall
|
||||||
|
from src.maisaka.message_adapter import build_message, get_message_kind, get_message_role, get_tool_call_id, get_tool_calls
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
if str(PROJECT_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_message_returns_session_message_with_maisaka_metadata() -> None:
|
||||||
|
timestamp = datetime.now()
|
||||||
|
tool_call = ToolCall(
|
||||||
|
call_id="call-1",
|
||||||
|
func_name="reply",
|
||||||
|
args={"message_id": "msg-1"},
|
||||||
|
)
|
||||||
|
raw_message = MessageSequence(components=[TextComponent(text="内部消息内容")])
|
||||||
|
|
||||||
|
message = build_message(
|
||||||
|
role="assistant",
|
||||||
|
content="展示消息内容",
|
||||||
|
message_kind="perception",
|
||||||
|
source="assistant",
|
||||||
|
tool_call_id="call-1",
|
||||||
|
tool_calls=[tool_call],
|
||||||
|
timestamp=timestamp,
|
||||||
|
message_id="maisaka-msg-1",
|
||||||
|
raw_message=raw_message,
|
||||||
|
display_text="展示消息内容",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(message, SessionMessage)
|
||||||
|
assert message.initialized is True
|
||||||
|
assert message.message_id == "maisaka-msg-1"
|
||||||
|
assert message.timestamp == timestamp
|
||||||
|
assert message.processed_plain_text == "展示消息内容"
|
||||||
|
assert message.display_message == "展示消息内容"
|
||||||
|
assert message.raw_message is raw_message
|
||||||
|
|
||||||
|
assert get_message_role(message) == "assistant"
|
||||||
|
assert get_message_kind(message) == "perception"
|
||||||
|
assert get_tool_call_id(message) == "call-1"
|
||||||
|
|
||||||
|
restored_tool_calls = get_tool_calls(message)
|
||||||
|
assert len(restored_tool_calls) == 1
|
||||||
|
assert restored_tool_calls[0].call_id == "call-1"
|
||||||
|
assert restored_tool_calls[0].func_name == "reply"
|
||||||
|
assert restored_tool_calls[0].args == {"message_id": "msg-1"}
|
||||||
@@ -343,14 +343,13 @@ class ChatBot:
|
|||||||
|
|
||||||
# message.update_chat_stream(chat)
|
# message.update_chat_stream(chat)
|
||||||
|
|
||||||
# 命令处理 - 使用新插件系统检查并处理命令
|
# 命令处理 - 使用新插件系统检查并处理命令。
|
||||||
# 注意:命令返回的 response 当前只用于日志记录和流程判断,
|
# 命令处理器内部自行决定是否回复消息,这里只负责流程分发与拦截。
|
||||||
# 不会在这里自动作为回复消息发送回会话。
|
is_command, cmd_result, continue_process = await self._process_commands(message)
|
||||||
# is_command, cmd_result, continue_process = await self._process_commands(message)
|
|
||||||
|
|
||||||
# # 如果是命令且不需要继续处理,则直接返回
|
# 如果是命令且不需要继续处理,则直接返回,避免落入 HeartFlow / MaiSaka。
|
||||||
# if is_command and await self._handle_command_processing_result(message, cmd_result, continue_process):
|
if is_command and await self._handle_command_processing_result(message, cmd_result, continue_process):
|
||||||
# return
|
return
|
||||||
|
|
||||||
# continue_flag, modified_message = await events_manager.handle_mai_events(EventType.ON_MESSAGE, message)
|
# continue_flag, modified_message = await events_manager.handle_mai_events(EventType.ON_MESSAGE, message)
|
||||||
# if not continue_flag:
|
# if not continue_flag:
|
||||||
|
|||||||
@@ -328,17 +328,17 @@ class LLMOrchestrator:
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] generate_response_with_message_async started "
|
f"LLMOrchestrator[{self.request_type}] 开始执行 generate_response_with_message_async "
|
||||||
f"(temperature={temperature}, max_tokens={max_tokens}, tools={len(tools or [])})"
|
f"(temperature={temperature}, max_tokens={max_tokens}, tools={len(tools or [])})"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] building internal tool options from {len(tools or [])} tool(s)"
|
f"LLMOrchestrator[{self.request_type}] 正在根据 {len(tools or [])} 个工具构建内部工具选项"
|
||||||
)
|
)
|
||||||
tool_built = self._build_tool_options(tools)
|
tool_built = self._build_tool_options(tools)
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(f"LLMOrchestrator[{self.request_type}] built {len(tool_built or [])} internal tool option(s)")
|
logger.info(f"LLMOrchestrator[{self.request_type}] 已构建 {len(tool_built or [])} 个内部工具选项")
|
||||||
|
|
||||||
execution_result = await self._execute_request(
|
execution_result = await self._execute_request(
|
||||||
request_type=RequestType.RESPONSE,
|
request_type=RequestType.RESPONSE,
|
||||||
@@ -353,7 +353,7 @@ class LLMOrchestrator:
|
|||||||
model_info = execution_result.model_info
|
model_info = execution_result.model_info
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] generate_response_with_message_async finished "
|
f"LLMOrchestrator[{self.request_type}] generate_response_with_message_async 执行完成 "
|
||||||
f"(model={model_info.name}, time_cost={time.time() - start_time:.2f}s)"
|
f"(model={model_info.name}, time_cost={time.time() - start_time:.2f}s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -832,18 +832,18 @@ class LLMOrchestrator:
|
|||||||
model_info, api_provider, client = self._select_model(exclude_models=failed_models_this_request)
|
model_info, api_provider, client = self._select_model(exclude_models=failed_models_this_request)
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] selected model={model_info.name} "
|
f"LLMOrchestrator[{self.request_type}] 已选择模型 model={model_info.name} "
|
||||||
f"provider={api_provider.name} request_type={request_type.value}"
|
f"provider={api_provider.name} request_type={request_type.value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
message_list = []
|
message_list = []
|
||||||
if message_factory:
|
if message_factory:
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(f"LLMOrchestrator[{self.request_type}] building message list via message_factory")
|
logger.info(f"LLMOrchestrator[{self.request_type}] 正在通过 message_factory 构建消息列表")
|
||||||
message_list = message_factory(client)
|
message_list = message_factory(client)
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] message_factory returned {len(message_list)} message(s)"
|
f"LLMOrchestrator[{self.request_type}] message_factory 返回了 {len(message_list)} 条消息"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -863,8 +863,8 @@ class LLMOrchestrator:
|
|||||||
)
|
)
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] sending request to model={model_info.name} "
|
f"LLMOrchestrator[{self.request_type}] 正在向模型 model={model_info.name} 发送请求 "
|
||||||
f"with tool_options={len(tool_options or [])}"
|
f"(tool_options={len(tool_options or [])})"
|
||||||
)
|
)
|
||||||
response = await self._attempt_request_on_model(
|
response = await self._attempt_request_on_model(
|
||||||
api_provider,
|
api_provider,
|
||||||
@@ -873,7 +873,7 @@ class LLMOrchestrator:
|
|||||||
)
|
)
|
||||||
if self.request_type.startswith("maisaka_"):
|
if self.request_type.startswith("maisaka_"):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"LLMOrchestrator[{self.request_type}] model={model_info.name} returned API response"
|
f"LLMOrchestrator[{self.request_type}] 模型 model={model_info.name} 已返回 API 响应"
|
||||||
)
|
)
|
||||||
total_tokens, penalty, usage_penalty = self.model_usage[model_info.name]
|
total_tokens, penalty, usage_penalty = self.model_usage[model_info.name]
|
||||||
if response_usage := response.usage:
|
if response_usage := response.usage:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from rich.markdown import Markdown
|
|||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from src.common.data_models.mai_message_data_model import MaiMessage
|
from src.chat.message_receive.message import SessionMessage
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
|
|
||||||
from .config import (
|
from .config import (
|
||||||
@@ -26,8 +26,8 @@ from .config import (
|
|||||||
from .input_reader import InputReader
|
from .input_reader import InputReader
|
||||||
from .knowledge import retrieve_relevant_knowledge
|
from .knowledge import retrieve_relevant_knowledge
|
||||||
from .knowledge_store import get_knowledge_store
|
from .knowledge_store import get_knowledge_store
|
||||||
from .llm_service import MaiSakaLLMService, build_message, remove_last_perception
|
from .llm_service import MaiSakaLLMService
|
||||||
from .message_adapter import format_speaker_content
|
from .message_adapter import build_message, format_speaker_content, remove_last_perception
|
||||||
from .mcp_client import MCPManager
|
from .mcp_client import MCPManager
|
||||||
from .tool_handlers import (
|
from .tool_handlers import (
|
||||||
ToolHandlerContext,
|
ToolHandlerContext,
|
||||||
@@ -47,7 +47,7 @@ class BufferCLI:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.llm_service: Optional[MaiSakaLLMService] = None
|
self.llm_service: Optional[MaiSakaLLMService] = None
|
||||||
self._reader = InputReader()
|
self._reader = InputReader()
|
||||||
self._chat_history: Optional[list[MaiMessage]] = None
|
self._chat_history: Optional[list[SessionMessage]] = None
|
||||||
self._knowledge_store = get_knowledge_store()
|
self._knowledge_store = get_knowledge_store()
|
||||||
|
|
||||||
knowledge_stats = self._knowledge_store.get_stats()
|
knowledge_stats = self._knowledge_store.get_stats()
|
||||||
@@ -122,7 +122,7 @@ class BufferCLI:
|
|||||||
|
|
||||||
await self._run_llm_loop(self._chat_history)
|
await self._run_llm_loop(self._chat_history)
|
||||||
|
|
||||||
async def _run_llm_loop(self, chat_history: list[MaiMessage]):
|
async def _run_llm_loop(self, chat_history: list[SessionMessage]):
|
||||||
"""
|
"""
|
||||||
Main inner loop for the Maisaka planner.
|
Main inner loop for the Maisaka planner.
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ class BufferCLI:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _generate_visible_reply(self, chat_history: list[MaiMessage], latest_thought: str) -> str:
|
async def _generate_visible_reply(self, chat_history: list[SessionMessage], latest_thought: str) -> str:
|
||||||
"""Generate and emit a visible reply based on the latest thought."""
|
"""Generate and emit a visible reply based on the latest thought."""
|
||||||
if not self.llm_service or not latest_thought:
|
if not self.llm_service or not latest_thought:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ MaiSaka knowledge retrieval helpers.
|
|||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from src.common.data_models.mai_message_data_model import MaiMessage
|
from src.chat.message_receive.message import SessionMessage
|
||||||
|
|
||||||
from .knowledge_store import KNOWLEDGE_CATEGORIES, get_knowledge_store
|
from .knowledge_store import KNOWLEDGE_CATEGORIES, get_knowledge_store
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ def extract_category_ids_from_result(result: str) -> List[str]:
|
|||||||
|
|
||||||
async def retrieve_relevant_knowledge(
|
async def retrieve_relevant_knowledge(
|
||||||
llm_service,
|
llm_service,
|
||||||
chat_history: List[MaiMessage],
|
chat_history: List[SessionMessage],
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Retrieve formatted knowledge snippets relevant to the current chat history."""
|
"""Retrieve formatted knowledge snippets relevant to the current chat history."""
|
||||||
store = get_knowledge_store()
|
store = get_knowledge_store()
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ from rich.panel import Panel
|
|||||||
from rich.pretty import Pretty
|
from rich.pretty import Pretty
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from src.common.data_models.mai_message_data_model import MaiMessage
|
from src.chat.message_receive.message import SessionMessage
|
||||||
|
from src.common.data_models.llm_service_data_models import LLMGenerationOptions
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.common.prompt_i18n import load_prompt
|
from src.common.prompt_i18n import load_prompt
|
||||||
from src.config.config import config_manager, global_config
|
from src.config.config import config_manager, global_config
|
||||||
@@ -31,7 +32,6 @@ from src.llm_models.payload_content.tool_option import (
|
|||||||
ToolOption,
|
ToolOption,
|
||||||
normalize_tool_options,
|
normalize_tool_options,
|
||||||
)
|
)
|
||||||
from src.common.data_models.llm_service_data_models import LLMGenerationOptions
|
|
||||||
from src.services.llm_service import LLMServiceClient
|
from src.services.llm_service import LLMServiceClient
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
@@ -55,7 +55,7 @@ class ChatResponse:
|
|||||||
|
|
||||||
content: Optional[str]
|
content: Optional[str]
|
||||||
tool_calls: List[ToolCall]
|
tool_calls: List[ToolCall]
|
||||||
raw_message: MaiMessage
|
raw_message: SessionMessage
|
||||||
|
|
||||||
|
|
||||||
class MaiSakaLLMService:
|
class MaiSakaLLMService:
|
||||||
@@ -428,7 +428,7 @@ class MaiSakaLLMService:
|
|||||||
padding=(0, 1),
|
padding=(0, 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def chat_loop_step(self, chat_history: List[MaiMessage]) -> ChatResponse:
|
async def chat_loop_step(self, chat_history: List[SessionMessage]) -> ChatResponse:
|
||||||
"""执行主对话循环的一步。
|
"""执行主对话循环的一步。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -514,7 +514,7 @@ class MaiSakaLLMService:
|
|||||||
source="assistant",
|
source="assistant",
|
||||||
tool_calls=tool_calls or None,
|
tool_calls=tool_calls or None,
|
||||||
)
|
)
|
||||||
logger.info("已将规划模型响应转换为 MaiMessage")
|
logger.info("已将规划模型响应转换为 SessionMessage")
|
||||||
|
|
||||||
return ChatResponse(
|
return ChatResponse(
|
||||||
content=response,
|
content=response,
|
||||||
@@ -522,7 +522,7 @@ class MaiSakaLLMService:
|
|||||||
raw_message=raw_message,
|
raw_message=raw_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _filter_for_api(self, chat_history: List[MaiMessage]) -> str:
|
def _filter_for_api(self, chat_history: List[SessionMessage]) -> str:
|
||||||
"""将对话历史过滤为简单文本格式。
|
"""将对话历史过滤为简单文本格式。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -555,14 +555,14 @@ class MaiSakaLLMService:
|
|||||||
|
|
||||||
return "\n\n".join(parts)
|
return "\n\n".join(parts)
|
||||||
|
|
||||||
def build_chat_context(self, user_text: str) -> List[MaiMessage]:
|
def build_chat_context(self, user_text: str) -> List[SessionMessage]:
|
||||||
"""构建新的对话上下文。
|
"""构建新的对话上下文。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_text: 用户输入文本。
|
user_text: 用户输入文本。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[MaiMessage]: 初始对话上下文消息列表。
|
List[SessionMessage]: 初始对话上下文消息列表。
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
build_message(
|
build_message(
|
||||||
@@ -572,7 +572,7 @@ class MaiSakaLLMService:
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
async def _removed_analyze_timing(self, chat_history: List[MaiMessage], timing_info: str) -> str:
|
async def _removed_analyze_timing(self, chat_history: List[SessionMessage], timing_info: str) -> str:
|
||||||
"""执行时间节奏分析。
|
"""执行时间节奏分析。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -623,7 +623,7 @@ class MaiSakaLLMService:
|
|||||||
|
|
||||||
# ──────── 回复生成(使用 replyer 模型) ────────
|
# ──────── 回复生成(使用 replyer 模型) ────────
|
||||||
|
|
||||||
async def generate_reply(self, reason: str, chat_history: List[MaiMessage]) -> str:
|
async def generate_reply(self, reason: str, chat_history: List[SessionMessage]) -> str:
|
||||||
"""生成最终回复文本。
|
"""生成最终回复文本。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
MaiSaka message adapters built on top of the main project's MaiMessage model.
|
MaiSaka 内部消息适配器。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@@ -12,7 +12,8 @@ import re
|
|||||||
|
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
from src.common.data_models.mai_message_data_model import GroupInfo, MaiMessage, MessageInfo, UserInfo
|
from src.chat.message_receive.message import SessionMessage
|
||||||
|
from src.common.data_models.mai_message_data_model import GroupInfo, MessageInfo, UserInfo
|
||||||
from src.common.data_models.message_component_data_model import EmojiComponent, ImageComponent, MessageSequence, TextComponent
|
from src.common.data_models.message_component_data_model import EmojiComponent, ImageComponent, MessageSequence, TextComponent
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.llm_models.payload_content.message import Message, MessageBuilder, RoleType
|
from src.llm_models.payload_content.message import Message, MessageBuilder, RoleType
|
||||||
@@ -77,11 +78,11 @@ def build_message(
|
|||||||
group_info: Optional[GroupInfo] = None,
|
group_info: Optional[GroupInfo] = None,
|
||||||
raw_message: Optional[MessageSequence] = None,
|
raw_message: Optional[MessageSequence] = None,
|
||||||
display_text: Optional[str] = None,
|
display_text: Optional[str] = None,
|
||||||
) -> MaiMessage:
|
) -> SessionMessage:
|
||||||
"""Build a MaiMessage for the Maisaka session history."""
|
"""为 MaiSaka 会话历史构建内部 ``SessionMessage``。"""
|
||||||
resolved_timestamp = timestamp or datetime.now()
|
resolved_timestamp = timestamp or datetime.now()
|
||||||
resolved_role = role.value if isinstance(role, RoleType) else role
|
resolved_role = role.value if isinstance(role, RoleType) else role
|
||||||
message = MaiMessage(
|
message = SessionMessage(
|
||||||
message_id=message_id or f"maisaka_{uuid4().hex}",
|
message_id=message_id or f"maisaka_{uuid4().hex}",
|
||||||
timestamp=resolved_timestamp,
|
timestamp=resolved_timestamp,
|
||||||
platform=platform,
|
platform=platform,
|
||||||
@@ -104,6 +105,7 @@ def build_message(
|
|||||||
visible_text = display_text if display_text is not None else content
|
visible_text = display_text if display_text is not None else content
|
||||||
message.processed_plain_text = visible_text
|
message.processed_plain_text = visible_text
|
||||||
message.display_message = visible_text
|
message.display_message = visible_text
|
||||||
|
message.initialized = True
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ def _guess_image_format(image_bytes: bytes) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_message_text(message: MaiMessage) -> str:
|
def get_message_text(message: SessionMessage) -> str:
|
||||||
if message.processed_plain_text is not None:
|
if message.processed_plain_text is not None:
|
||||||
return message.processed_plain_text
|
return message.processed_plain_text
|
||||||
if message.display_message is not None:
|
if message.display_message is not None:
|
||||||
@@ -174,42 +176,42 @@ def get_message_text(message: MaiMessage) -> str:
|
|||||||
return "".join(parts)
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def get_message_role(message: MaiMessage) -> str:
|
def get_message_role(message: SessionMessage) -> str:
|
||||||
return str(message.message_info.additional_config.get(LLM_ROLE_KEY, RoleType.User.value))
|
return str(message.message_info.additional_config.get(LLM_ROLE_KEY, RoleType.User.value))
|
||||||
|
|
||||||
|
|
||||||
def get_message_kind(message: MaiMessage) -> str:
|
def get_message_kind(message: SessionMessage) -> str:
|
||||||
return str(message.message_info.additional_config.get(MESSAGE_KIND_KEY, "normal"))
|
return str(message.message_info.additional_config.get(MESSAGE_KIND_KEY, "normal"))
|
||||||
|
|
||||||
|
|
||||||
def get_message_source(message: MaiMessage) -> str:
|
def get_message_source(message: SessionMessage) -> str:
|
||||||
return str(message.message_info.additional_config.get(SOURCE_KEY, get_message_role(message)))
|
return str(message.message_info.additional_config.get(SOURCE_KEY, get_message_role(message)))
|
||||||
|
|
||||||
|
|
||||||
def is_perception_message(message: MaiMessage) -> bool:
|
def is_perception_message(message: SessionMessage) -> bool:
|
||||||
return get_message_kind(message) == "perception"
|
return get_message_kind(message) == "perception"
|
||||||
|
|
||||||
|
|
||||||
def get_tool_call_id(message: MaiMessage) -> Optional[str]:
|
def get_tool_call_id(message: SessionMessage) -> Optional[str]:
|
||||||
value = message.message_info.additional_config.get(TOOL_CALL_ID_KEY)
|
value = message.message_info.additional_config.get(TOOL_CALL_ID_KEY)
|
||||||
return str(value) if value else None
|
return str(value) if value else None
|
||||||
|
|
||||||
|
|
||||||
def get_tool_calls(message: MaiMessage) -> list[ToolCall]:
|
def get_tool_calls(message: SessionMessage) -> list[ToolCall]:
|
||||||
raw_tool_calls = message.message_info.additional_config.get(TOOL_CALLS_KEY, [])
|
raw_tool_calls = message.message_info.additional_config.get(TOOL_CALLS_KEY, [])
|
||||||
if not isinstance(raw_tool_calls, list):
|
if not isinstance(raw_tool_calls, list):
|
||||||
return []
|
return []
|
||||||
return [_deserialize_tool_call(item) for item in raw_tool_calls if isinstance(item, dict)]
|
return [_deserialize_tool_call(item) for item in raw_tool_calls if isinstance(item, dict)]
|
||||||
|
|
||||||
|
|
||||||
def remove_last_perception(messages: list[MaiMessage]) -> None:
|
def remove_last_perception(messages: list[SessionMessage]) -> None:
|
||||||
for index in range(len(messages) - 1, -1, -1):
|
for index in range(len(messages) - 1, -1, -1):
|
||||||
if is_perception_message(messages[index]):
|
if is_perception_message(messages[index]):
|
||||||
messages.pop(index)
|
messages.pop(index)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def to_llm_message(message: MaiMessage) -> Optional[Message]:
|
def to_llm_message(message: SessionMessage) -> Optional[Message]:
|
||||||
role = get_message_role(message)
|
role = get_message_role(message)
|
||||||
tool_call_id = get_tool_call_id(message)
|
tool_call_id = get_tool_call_id(message)
|
||||||
tool_calls = get_tool_calls(message)
|
tool_calls = get_tool_calls(message)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ MaiSaka reply helper.
|
|||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from src.common.data_models.mai_message_data_model import MaiMessage
|
from src.chat.message_receive.message import SessionMessage
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
|
|
||||||
from .config import USER_NAME
|
from .config import USER_NAME
|
||||||
@@ -19,17 +19,17 @@ def _normalize_content(content: str, limit: int = 500) -> str:
|
|||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
def _format_message_time(message: MaiMessage) -> str:
|
def _format_message_time(message: SessionMessage) -> str:
|
||||||
return message.timestamp.strftime("%H:%M:%S")
|
return message.timestamp.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
def _extract_visible_assistant_reply(message: MaiMessage) -> str:
|
def _extract_visible_assistant_reply(message: SessionMessage) -> str:
|
||||||
if is_perception_message(message):
|
if is_perception_message(message):
|
||||||
return ""
|
return ""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _extract_guided_bot_reply(message: MaiMessage) -> str:
|
def _extract_guided_bot_reply(message: SessionMessage) -> str:
|
||||||
speaker_name, body = parse_speaker_content(get_message_text(message).strip())
|
speaker_name, body = parse_speaker_content(get_message_text(message).strip())
|
||||||
bot_nickname = global_config.bot.nickname.strip() or "Bot"
|
bot_nickname = global_config.bot.nickname.strip() or "Bot"
|
||||||
if speaker_name == bot_nickname:
|
if speaker_name == bot_nickname:
|
||||||
@@ -64,7 +64,7 @@ def _split_user_message_segments(raw_content: str) -> list[tuple[Optional[str],
|
|||||||
return segments
|
return segments
|
||||||
|
|
||||||
|
|
||||||
def format_chat_history(messages: list[MaiMessage]) -> str:
|
def format_chat_history(messages: list[SessionMessage]) -> str:
|
||||||
"""Format visible chat history for reply generation."""
|
"""Format visible chat history for reply generation."""
|
||||||
bot_nickname = global_config.bot.nickname.strip() or "Bot"
|
bot_nickname = global_config.bot.nickname.strip() or "Bot"
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
@@ -109,7 +109,7 @@ class Replyer:
|
|||||||
def set_enabled(self, enabled: bool) -> None:
|
def set_enabled(self, enabled: bool) -> None:
|
||||||
self._enabled = enabled
|
self._enabled = enabled
|
||||||
|
|
||||||
async def reply(self, reason: str, chat_history: list[MaiMessage]) -> str:
|
async def reply(self, reason: str, chat_history: list[SessionMessage]) -> str:
|
||||||
if not self._enabled or not reason or self._llm_service is None:
|
if not self._enabled or not reason or self._llm_service is None:
|
||||||
return "..."
|
return "..."
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import asyncio
|
|||||||
from src.chat.heart_flow.heartFC_utils import CycleDetail
|
from src.chat.heart_flow.heartFC_utils import CycleDetail
|
||||||
from src.chat.message_receive.chat_manager import BotChatSession, chat_manager
|
from src.chat.message_receive.chat_manager import BotChatSession, chat_manager
|
||||||
from src.chat.message_receive.message import SessionMessage
|
from src.chat.message_receive.message import SessionMessage
|
||||||
from src.common.data_models.mai_message_data_model import GroupInfo, MaiMessage, UserInfo
|
from src.common.data_models.mai_message_data_model import GroupInfo, UserInfo
|
||||||
from src.common.data_models.message_component_data_model import MessageSequence
|
from src.common.data_models.message_component_data_model import MessageSequence
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
@@ -56,7 +56,7 @@ class MaisakaHeartFlowChatting:
|
|||||||
session_name = chat_manager.get_session_name(session_id) or session_id
|
session_name = chat_manager.get_session_name(session_id) or session_id
|
||||||
self.log_prefix = f"[{session_name}]"
|
self.log_prefix = f"[{session_name}]"
|
||||||
self._llm_service = MaiSakaLLMService(api_key="", base_url=None, model="")
|
self._llm_service = MaiSakaLLMService(api_key="", base_url=None, model="")
|
||||||
self._chat_history: list[MaiMessage] = []
|
self._chat_history: list[SessionMessage] = []
|
||||||
self.history_loop: list[CycleDetail] = []
|
self.history_loop: list[CycleDetail] = []
|
||||||
self.message_cache: list[SessionMessage] = []
|
self.message_cache: list[SessionMessage] = []
|
||||||
self._mcp_manager: Optional[MCPManager] = None
|
self._mcp_manager: Optional[MCPManager] = None
|
||||||
@@ -227,7 +227,7 @@ class MaisakaHeartFlowChatting:
|
|||||||
|
|
||||||
return merged_sequence
|
return merged_sequence
|
||||||
|
|
||||||
async def _build_user_history_message(self, message: SessionMessage) -> Optional[MaiMessage]:
|
async def _build_user_history_message(self, message: SessionMessage) -> Optional[SessionMessage]:
|
||||||
user_sequence = await self._build_message_sequence(message)
|
user_sequence = await self._build_message_sequence(message)
|
||||||
visible_text = build_visible_text_from_sequence(user_sequence).strip()
|
visible_text = build_visible_text_from_sequence(user_sequence).strip()
|
||||||
if not user_sequence.components:
|
if not user_sequence.components:
|
||||||
@@ -498,7 +498,7 @@ class MaisakaHeartFlowChatting:
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _build_tool_message(self, tool_call: ToolCall, content: str) -> MaiMessage:
|
def _build_tool_message(self, tool_call: ToolCall, content: str) -> SessionMessage:
|
||||||
return build_message(
|
return build_message(
|
||||||
role="tool",
|
role="tool",
|
||||||
content=content,
|
content=content,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import os
|
|||||||
|
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
|
||||||
from src.common.data_models.mai_message_data_model import MaiMessage
|
from src.chat.message_receive.message import SessionMessage
|
||||||
from src.llm_models.payload_content.tool_option import ToolCall
|
from src.llm_models.payload_content.tool_option import ToolCall
|
||||||
|
|
||||||
from .config import console
|
from .config import console
|
||||||
@@ -41,7 +41,7 @@ class ToolHandlerContext:
|
|||||||
self.last_user_input_time: Optional[datetime] = None
|
self.last_user_input_time: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
async def handle_stop(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
|
async def handle_stop(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
|
||||||
"""Handle the stop tool."""
|
"""Handle the stop tool."""
|
||||||
console.print("[accent]Calling tool: stop()[/accent]")
|
console.print("[accent]Calling tool: stop()[/accent]")
|
||||||
chat_history.append(
|
chat_history.append(
|
||||||
@@ -49,7 +49,7 @@ async def handle_stop(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_wait(tc: ToolCall, chat_history: list[MaiMessage], ctx: ToolHandlerContext) -> str:
|
async def handle_wait(tc: ToolCall, chat_history: list[SessionMessage], ctx: ToolHandlerContext) -> str:
|
||||||
"""Handle the wait tool."""
|
"""Handle the wait tool."""
|
||||||
seconds = (tc.args or {}).get("seconds", 30)
|
seconds = (tc.args or {}).get("seconds", 30)
|
||||||
seconds = max(5, min(seconds, 300))
|
seconds = max(5, min(seconds, 300))
|
||||||
@@ -86,7 +86,7 @@ async def _do_wait(seconds: int, ctx: ToolHandlerContext) -> str:
|
|||||||
return f"User input received: {user_input}"
|
return f"User input received: {user_input}"
|
||||||
|
|
||||||
|
|
||||||
async def handle_mcp_tool(tc: ToolCall, chat_history: list[MaiMessage], mcp_manager: "MCPManager") -> None:
|
async def handle_mcp_tool(tc: ToolCall, chat_history: list[SessionMessage], mcp_manager: "MCPManager") -> None:
|
||||||
"""Handle an MCP tool call."""
|
"""Handle an MCP tool call."""
|
||||||
args_str = _json.dumps(tc.args or {}, ensure_ascii=False)
|
args_str = _json.dumps(tc.args or {}, ensure_ascii=False)
|
||||||
args_preview = args_str if len(args_str) <= 120 else args_str[:120] + "..."
|
args_preview = args_str if len(args_str) <= 120 else args_str[:120] + "..."
|
||||||
@@ -107,13 +107,13 @@ async def handle_mcp_tool(tc: ToolCall, chat_history: list[MaiMessage], mcp_mana
|
|||||||
chat_history.append(build_message(role="tool", content=result, tool_call_id=tc.call_id))
|
chat_history.append(build_message(role="tool", content=result, tool_call_id=tc.call_id))
|
||||||
|
|
||||||
|
|
||||||
async def handle_unknown_tool(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
|
async def handle_unknown_tool(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
|
||||||
"""Handle an unknown tool call."""
|
"""Handle an unknown tool call."""
|
||||||
console.print(f"[accent]Calling unknown tool: {tc.func_name}({tc.args})[/accent]")
|
console.print(f"[accent]Calling unknown tool: {tc.func_name}({tc.args})[/accent]")
|
||||||
chat_history.append(build_message(role="tool", content=f"Unknown tool: {tc.func_name}", tool_call_id=tc.call_id))
|
chat_history.append(build_message(role="tool", content=f"Unknown tool: {tc.func_name}", tool_call_id=tc.call_id))
|
||||||
|
|
||||||
|
|
||||||
async def handle_write_file(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
|
async def handle_write_file(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
|
||||||
"""Write a file under the local mai_files workspace."""
|
"""Write a file under the local mai_files workspace."""
|
||||||
filename = (tc.args or {}).get("filename", "")
|
filename = (tc.args or {}).get("filename", "")
|
||||||
content = (tc.args or {}).get("content", "")
|
content = (tc.args or {}).get("content", "")
|
||||||
@@ -149,7 +149,7 @@ async def handle_write_file(tc: ToolCall, chat_history: list[MaiMessage]) -> Non
|
|||||||
chat_history.append(build_message(role="tool", content=error_msg, tool_call_id=tc.call_id))
|
chat_history.append(build_message(role="tool", content=error_msg, tool_call_id=tc.call_id))
|
||||||
|
|
||||||
|
|
||||||
async def handle_read_file(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
|
async def handle_read_file(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
|
||||||
"""Read a file from the local mai_files workspace."""
|
"""Read a file from the local mai_files workspace."""
|
||||||
filename = (tc.args or {}).get("filename", "")
|
filename = (tc.args or {}).get("filename", "")
|
||||||
console.print(f'[accent]Calling tool: read_file("{filename}")[/accent]')
|
console.print(f'[accent]Calling tool: read_file("{filename}")[/accent]')
|
||||||
@@ -190,7 +190,7 @@ async def handle_read_file(tc: ToolCall, chat_history: list[MaiMessage]) -> None
|
|||||||
chat_history.append(build_message(role="tool", content=error_msg, tool_call_id=tc.call_id))
|
chat_history.append(build_message(role="tool", content=error_msg, tool_call_id=tc.call_id))
|
||||||
|
|
||||||
|
|
||||||
async def handle_list_files(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
|
async def handle_list_files(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
|
||||||
"""List files under the local mai_files workspace."""
|
"""List files under the local mai_files workspace."""
|
||||||
console.print("[accent]Calling tool: list_files()[/accent]")
|
console.print("[accent]Calling tool: list_files()[/accent]")
|
||||||
|
|
||||||
|
|||||||
@@ -139,27 +139,18 @@ class ComponentQueryService:
|
|||||||
metadata = dict(entry.metadata)
|
metadata = dict(entry.metadata)
|
||||||
raw_action_parameters = metadata.get("action_parameters")
|
raw_action_parameters = metadata.get("action_parameters")
|
||||||
action_parameters = (
|
action_parameters = (
|
||||||
{
|
{str(param_name): str(param_description) for param_name, param_description in raw_action_parameters.items()}
|
||||||
str(param_name): str(param_description)
|
|
||||||
for param_name, param_description in raw_action_parameters.items()
|
|
||||||
}
|
|
||||||
if isinstance(raw_action_parameters, dict)
|
if isinstance(raw_action_parameters, dict)
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
action_require = [
|
action_require = [
|
||||||
str(item)
|
str(item) for item in (metadata.get("action_require") or []) if item is not None and str(item).strip()
|
||||||
for item in (metadata.get("action_require") or [])
|
|
||||||
if item is not None and str(item).strip()
|
|
||||||
]
|
]
|
||||||
associated_types = [
|
associated_types = [
|
||||||
str(item)
|
str(item) for item in (metadata.get("associated_types") or []) if item is not None and str(item).strip()
|
||||||
for item in (metadata.get("associated_types") or [])
|
|
||||||
if item is not None and str(item).strip()
|
|
||||||
]
|
]
|
||||||
activation_keywords = [
|
activation_keywords = [
|
||||||
str(item)
|
str(item) for item in (metadata.get("activation_keywords") or []) if item is not None and str(item).strip()
|
||||||
for item in (metadata.get("activation_keywords") or [])
|
|
||||||
if item is not None and str(item).strip()
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return ActionInfo(
|
return ActionInfo(
|
||||||
@@ -442,9 +433,14 @@ class ComponentQueryService:
|
|||||||
message = kwargs.get("message")
|
message = kwargs.get("message")
|
||||||
matched_groups = kwargs.get("matched_groups")
|
matched_groups = kwargs.get("matched_groups")
|
||||||
plugin_config = kwargs.get("plugin_config")
|
plugin_config = kwargs.get("plugin_config")
|
||||||
|
message_info = getattr(message, "message_info", None)
|
||||||
|
group_info = getattr(message_info, "group_info", None)
|
||||||
|
user_info = getattr(message_info, "user_info", None)
|
||||||
invoke_args: Dict[str, Any] = {
|
invoke_args: Dict[str, Any] = {
|
||||||
"text": str(getattr(message, "processed_plain_text", "") or ""),
|
"text": str(getattr(message, "processed_plain_text", "") or ""),
|
||||||
"stream_id": str(getattr(message, "session_id", "") or ""),
|
"stream_id": str(getattr(message, "session_id", "") or ""),
|
||||||
|
"group_id": str(getattr(group_info, "group_id", "") or ""),
|
||||||
|
"user_id": str(getattr(user_info, "user_id", "") or ""),
|
||||||
"matched_groups": matched_groups if isinstance(matched_groups, dict) else {},
|
"matched_groups": matched_groups if isinstance(matched_groups, dict) else {},
|
||||||
}
|
}
|
||||||
if isinstance(plugin_config, dict):
|
if isinstance(plugin_config, dict):
|
||||||
|
|||||||
Reference in New Issue
Block a user