ref:让MaiSaka使用麦麦原有的pompt系统,配置系统

This commit is contained in:
SengokuCola
2026-03-11 21:25:35 +08:00
parent 6c32d17e21
commit 664f900f43
40 changed files with 230 additions and 221 deletions

View File

@@ -1,7 +1,7 @@
""" """
MaiSaka - 程序入口 MaiSaka - 程序入口
使用方法: 使用方法:
python main.py python saka.py
环境变量 (可通过 .env 文件设置): 环境变量 (可通过 .env 文件设置):
OPENAI_API_KEY - API 密钥 OPENAI_API_KEY - API 密钥
@@ -11,6 +11,16 @@ MaiSaka - 程序入口
""" """
import asyncio import asyncio
import sys
from pathlib import Path
# 添加项目根目录和 src/maisaka 到 Python 路径
_root = Path(__file__).parent
_maisaka_path = _root / "src" / "maisaka"
if str(_root) not in sys.path:
sys.path.insert(0, str(_root))
if str(_maisaka_path) not in sys.path:
sys.path.insert(0, str(_maisaka_path))
from config import console from config import console
from cli import BufferCLI from cli import BufferCLI

3
src/MaiDiary/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
"""
maisaka - MaiSaka 对话系统
"""

View File

@@ -1,46 +0,0 @@
"""
MaiSaka - 全局配置
环境变量加载、Rich Console 实例、主题定义。
"""
import os
from dotenv import load_dotenv
from rich.console import Console
from rich.theme import Theme
# ──────────────────── 加载 .env ────────────────────
load_dotenv()
# ──────────────────── 模块开关配置 ────────────────────
ENABLE_EMOTION_MODULE = os.getenv("ENABLE_EMOTION_MODULE", "true").strip().lower() == "true"
ENABLE_COGNITION_MODULE = os.getenv("ENABLE_COGNITION_MODULE", "true").strip().lower() == "true"
# Timing 模块已包含自我反思功能
ENABLE_TIMING_MODULE = os.getenv("ENABLE_TIMING_MODULE", "true").strip().lower() == "true"
ENABLE_KNOWLEDGE_MODULE = os.getenv("ENABLE_KNOWLEDGE_MODULE", "true").strip().lower() == "true"
ENABLE_MCP = os.getenv("ENABLE_MCP", "true").strip().lower() == "true"
ENABLE_WRITE_FILE = os.getenv("ENABLE_WRITE_FILE", "true").strip().lower() == "true"
ENABLE_READ_FILE = os.getenv("ENABLE_READ_FILE", "true").strip().lower() == "true"
ENABLE_LIST_FILES = os.getenv("ENABLE_LIST_FILES", "true").strip().lower() == "true"
# ──────────────────── QQ 工具配置 ────────────────────
ENABLE_QQ_TOOLS = os.getenv("ENABLE_QQ_TOOLS", "false").strip().lower() == "true"
QQ_API_BASE_URL = os.getenv("QQ_API_BASE_URL", "").strip()
QQ_API_KEY = os.getenv("QQ_API_KEY", "").strip()
# ──────────────────── Rich 主题 & Console ────────────────────
custom_theme = Theme(
{
"info": "cyan",
"success": "green",
"warning": "yellow",
"error": "bold red",
"muted": "dim",
"accent": "bold magenta",
}
)
console = Console(theme=custom_theme)

View File

@@ -1,58 +0,0 @@
# MaiSaka - LLM API 配置
# 复制本文件为 .env 并填入你的配置
# 必填: API 密钥
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
# 可选: API 基地址 (如使用第三方兼容接口或自建代理)
OPENAI_BASE_URL=https://api.openai.com/v1
# 可选: 模型名称 (默认 gpt-4o, 需支持视觉能力以处理图片)
OPENAI_MODEL=gpt-4o
# 可选: 是否启用 LLM 思考模式 (true/false, 不设置则不发送该参数)
# 设为 true 时允许 LLM 先进行思考再输出,设为 false 时直接输出
ENABLE_THINKING=true
# 可选: 是否启用情绪猜测模块 (true/false, 默认 true)
# 设为 false 时禁用情绪分析,可节省 API 调用成本
ENABLE_EMOTION_MODULE=true
# 可选: 是否启用认知感知模块 (true/false, 默认 true)
# 设为 false 时禁用意图分析,可节省 API 调用成本
ENABLE_COGNITION_MODULE=true
# 可选: 是否启用记忆模块 (true/false, 默认 true)
# 设为 false 时禁用记忆存储和检索功能
# 注意: 关闭记忆模块不会影响了解(Knowledge)模块
ENABLE_MEMORY_MODULE=true
# 可选: 是否启用 Timing 模块 (true/false, 默认 true)
# 设为 false 时禁用时间节奏分析,可节省 API 调用成本
# 注意: Timing 模块已包含自我反思功能
ENABLE_TIMING_MODULE=true
# 可选: 是否启用文件写入工具 (true/false, 默认 true)
# 设为 false 时禁用 write_file 工具
ENABLE_WRITE_FILE=false
# 可选: 是否启用文件读取工具 (true/false, 默认 true)
# 设为 false 时禁用 read_file 工具
ENABLE_READ_FILE=false
# 可选: 是否启用文件列表工具 (true/false, 默认 true)
# 设为 false 时禁用 list_files 工具
ENABLE_LIST_FILES=false
# 可选: 是否启用 QQ 工具 (true/false, 默认 false)
# 设为 true 时启用 get_qq_chat_info、send_info、list_qq_chats 工具
ENABLE_QQ_TOOLS=false
# 可选: QQ API 基地址 (启用 QQ_TOOLS 时必填)
# 指向提供 QQ 聊天功能的 HTTP 服务端点
# 示例: http://localhost:8017
QQ_API_BASE_URL=http://localhost:8017
# 可选: QQ API 密钥 (如果服务需要认证)
# 留空则不发送认证头
QQ_API_KEY=your-api-key

View File

@@ -1,84 +0,0 @@
"""
MaiSaka - Prompt 加载器
支持从 .prompt 文件加载模板,并进行变量替换。
"""
import os
from pathlib import Path
from typing import Any
class PromptLoader:
"""Prompt 模板加载器"""
def __init__(self, prompts_dir: str | None = None):
"""
初始化加载器。
Args:
prompts_dir: prompts 目录路径,默认为项目根目录下的 prompts/
"""
if prompts_dir is None:
# 默认为项目根目录下的 prompts/
project_root = Path(__file__).parent
prompts_dir = project_root / "prompts"
self.prompts_dir = Path(prompts_dir)
self._cache: dict[str, str] = {}
def load(self, name: str, **kwargs: Any) -> str:
"""
加载并渲染 prompt 模板。
Args:
name: 模板文件名(不含 .prompt 后缀)
**kwargs: 模板变量
Returns:
渲染后的 prompt 文本
"""
# 从缓存读取
if name not in self._cache:
template_path = self.prompts_dir / f"{name}.prompt"
if not template_path.exists():
raise FileNotFoundError(f"Prompt template not found: {template_path}")
self._cache[name] = template_path.read_text(encoding="utf-8")
template = self._cache[name]
# 变量替换
if kwargs:
try:
return template.format(**kwargs)
except KeyError as e:
raise ValueError(f"Missing template variable: {e}") from e
return template
def clear_cache(self):
"""清空缓存"""
self._cache.clear()
# 全局单例
_loader = PromptLoader()
def load_prompt(name: str, **kwargs: Any) -> str:
"""
加载并渲染 prompt 模板(全局函数)。
Args:
name: 模板文件名(不含 .prompt 后缀)
**kwargs: 模板变量
Returns:
渲染后的 prompt 文本
"""
return _loader.load(name, **kwargs)
def reload_prompts():
"""重新加载所有 prompt清空缓存"""
_loader.clear_cache()

View File

@@ -1,14 +0,0 @@
# 这是一个带变量替换的示例模板
# 使用 {variable_name} 语法定义变量
# 调用时使用 load_prompt("example_with_vars", name="麦麦", mood="开心")
你好 {name}
今天看起来你心情{mood}。
作为你的 AI 助手,我会:
1. {task1}
2. {task2}
3. {task3}
祝你今天愉快!

View File

@@ -31,6 +31,7 @@ from .official_configs import (
DebugConfig, DebugConfig,
WebUIConfig, WebUIConfig,
DatabaseConfig, DatabaseConfig,
MaiSakaConfig,
) )
from .model_configs import ModelInfo, ModelTaskConfig, APIProvider from .model_configs import ModelInfo, ModelTaskConfig, APIProvider
from .config_base import ConfigBase, Field, AttributeData from .config_base import ConfigBase, Field, AttributeData
@@ -127,6 +128,9 @@ class Config(ConfigBase):
database: DatabaseConfig = Field(default_factory=DatabaseConfig) database: DatabaseConfig = Field(default_factory=DatabaseConfig)
"""数据库配置类""" """数据库配置类"""
maisaka: MaiSakaConfig = Field(default_factory=MaiSakaConfig)
"""MaiSaka对话系统配置类"""
class ModelConfig(ConfigBase): class ModelConfig(ConfigBase):
"""模型配置类""" """模型配置类"""

View File

@@ -1501,3 +1501,110 @@ class DatabaseConfig(ConfigBase):
若禁用,则消息中的二进制将会在识别后删除,并在消息中使用识别结果替代,无法二次识别 若禁用,则消息中的二进制将会在识别后删除,并在消息中使用识别结果替代,无法二次识别
该配置项仅影响新存储的消息,已有消息不会受到影响 该配置项仅影响新存储的消息,已有消息不会受到影响
""" """
class MaiSakaConfig(ConfigBase):
"""MaiSaka 对话系统配置类"""
__ui_label__ = "MaiSaka"
__ui_icon__ = "message-circle"
__ui_parent__ = "experimental"
enable_emotion_module: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "heart",
},
)
"""启用情绪感知模块"""
enable_cognition_module: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "brain",
},
)
"""启用认知分析模块"""
enable_timing_module: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "clock",
},
)
"""启用时间感知模块(含自我反思功能)"""
enable_knowledge_module: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "book",
},
)
"""启用知识库模块"""
enable_mcp: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "zap",
},
)
"""启用 MCP (Model Context Protocol) 支持"""
enable_write_file: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "file-plus",
},
)
"""启用文件写入工具"""
enable_read_file: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "file-text",
},
)
"""启用文件读取工具"""
enable_list_files: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "list",
},
)
"""启用文件列表工具"""
enable_qq_tools: bool = Field(
default=False,
json_schema_extra={
"x-widget": "switch",
"x-icon": "users",
},
)
"""启用 QQ 工具(获取聊天记录、发送消息等)"""
qq_api_base_url: str = Field(
default="",
json_schema_extra={
"x-widget": "input",
"x-icon": "server",
},
)
"""QQ API 基地址"""
qq_api_key: str = Field(
default="",
json_schema_extra={
"x-widget": "input",
"x-icon": "key",
},
)
"""QQ API 密钥"""

61
src/maisaka/config.py Normal file
View File

@@ -0,0 +1,61 @@
"""
MaiSaka - 全局配置
从主项目配置系统读取配置、Rich Console 实例、主题定义。
"""
import sys
from pathlib import Path
from rich.console import Console
from rich.theme import Theme
# 添加项目根目录到路径以导入主配置
_root = Path(__file__).parent.parent.parent.absolute()
if str(_root) not in sys.path:
sys.path.insert(0, str(_root))
# ──────────────────── 从主配置读取 ────────────────────
def _get_maisaka_config():
"""获取 MaiSaka 配置"""
try:
from src.config.config import config_manager
return config_manager.config.maisaka
except Exception:
# 如果配置加载失败,返回默认值
from src.config.official_configs import MaiSakaConfig
return MaiSakaConfig()
_maisaka_config = _get_maisaka_config()
# ──────────────────── 模块开关配置 ────────────────────
ENABLE_EMOTION_MODULE = _maisaka_config.enable_emotion_module
ENABLE_COGNITION_MODULE = _maisaka_config.enable_cognition_module
# Timing 模块已包含自我反思功能
ENABLE_TIMING_MODULE = _maisaka_config.enable_timing_module
ENABLE_KNOWLEDGE_MODULE = _maisaka_config.enable_knowledge_module
ENABLE_MCP = _maisaka_config.enable_mcp
ENABLE_WRITE_FILE = _maisaka_config.enable_write_file
ENABLE_READ_FILE = _maisaka_config.enable_read_file
ENABLE_LIST_FILES = _maisaka_config.enable_list_files
# ──────────────────── QQ 工具配置 ────────────────────
ENABLE_QQ_TOOLS = _maisaka_config.enable_qq_tools
QQ_API_BASE_URL = _maisaka_config.qq_api_base_url
QQ_API_KEY = _maisaka_config.qq_api_key
# ──────────────────── Rich 主题 & Console ────────────────────
custom_theme = Theme(
{
"info": "cyan",
"success": "green",
"warning": "yellow",
"error": "bold red",
"muted": "dim",
"accent": "bold magenta",
}
)
console = Console(theme=custom_theme)

View File

@@ -2,8 +2,8 @@
MaiSaka - Emotion 模块 MaiSaka - Emotion 模块
情绪感知分析分析用户的情绪状态和言语态度 情绪感知分析分析用户的情绪状态和言语态度
注意EQ_SYSTEM_PROMPT 已迁移至 prompts/emotion.system.prompt 注意emotion.prompt 已迁移至主项目 prompts/ 目录
使用 prompt_loader.load_prompt("emotion.system") 加载 使用 prompt_manager.get_prompt("maidairy_emotion") 加载
""" """
from typing import List, Optional from typing import List, Optional

View File

@@ -8,13 +8,29 @@ from typing import Callable, List, Optional
from openai import AsyncOpenAI from openai import AsyncOpenAI
import asyncio
from .base import BaseLLMService, ChatResponse, ModelInfo, ToolCall from .base import BaseLLMService, ChatResponse, ModelInfo, ToolCall
from .prompts import get_enabled_chat_tools from .prompts import get_enabled_chat_tools
from .utils import format_chat_history, format_chat_history_for_eq, filter_for_api from .utils import format_chat_history, format_chat_history_for_eq, filter_for_api
from prompt_loader import load_prompt from src.prompt.prompt_manager import prompt_manager
from knowledge import extract_category_ids_from_result from knowledge import extract_category_ids_from_result
def _load_prompt_sync(name: str, **kwargs) -> str:
"""同步加载并渲染 prompt用于非异步上下文"""
prompt = prompt_manager.get_prompt(name)
for key, value in kwargs.items():
prompt.add_context(key, value)
# 在新事件循环中运行异步渲染
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
return loop.run_until_complete(prompt_manager.render_prompt(prompt))
finally:
loop.close()
class OpenAILLMService(BaseLLMService): class OpenAILLMService(BaseLLMService):
""" """
基于 OpenAI 兼容 API LLM 服务实现 基于 OpenAI 兼容 API LLM 服务实现
@@ -81,7 +97,7 @@ class OpenAILLMService(BaseLLMService):
tools_section = "" tools_section = ""
# 加载提示词模板并注入工具部分 # 加载提示词模板并注入工具部分
self._chat_system_prompt = load_prompt("chat.system", file_tools_section=tools_section) self._chat_system_prompt = _load_prompt_sync("maidairy_chat", file_tools_section=tools_section)
else: else:
self._chat_system_prompt = chat_system_prompt self._chat_system_prompt = chat_system_prompt
@@ -239,8 +255,9 @@ class OpenAILLMService(BaseLLMService):
if msg.get("_type") != "perception" and msg.get("role") != "system" if msg.get("_type") != "perception" and msg.get("role") != "system"
] ]
formatted = format_chat_history(filtered_history) formatted = format_chat_history(filtered_history)
timing_prompt = prompt_manager.get_prompt("maidairy_timing")
timing_messages = [ timing_messages = [
{"role": "system", "content": load_prompt("timing.system")}, {"role": "system", "content": await prompt_manager.render_prompt(timing_prompt)},
{ {
"role": "user", "role": "user",
"content": ( "content": (
@@ -272,8 +289,9 @@ class OpenAILLMService(BaseLLMService):
# 使用情商模块专用格式化函数:只包含用户回复、助手思考、助手说 # 使用情商模块专用格式化函数:只包含用户回复、助手思考、助手说
formatted = format_chat_history_for_eq(recent_messages) formatted = format_chat_history_for_eq(recent_messages)
emotion_prompt = prompt_manager.get_prompt("maidairy_emotion")
eq_messages = [ eq_messages = [
{"role": "system", "content": load_prompt("emotion.system")}, {"role": "system", "content": await prompt_manager.render_prompt(emotion_prompt)},
{ {
"role": "user", "role": "user",
"content": f"以下是最近几轮对话记录,请分析其中用户的情绪状态和言语态度:\n\n{formatted}", "content": f"以下是最近几轮对话记录,请分析其中用户的情绪状态和言语态度:\n\n{formatted}",
@@ -302,8 +320,9 @@ class OpenAILLMService(BaseLLMService):
# 使用情商模块专用格式化函数:只包含用户回复、助手思考、助手说 # 使用情商模块专用格式化函数:只包含用户回复、助手思考、助手说
formatted = format_chat_history_for_eq(recent_messages) formatted = format_chat_history_for_eq(recent_messages)
cognition_prompt = prompt_manager.get_prompt("maidairy_cognition")
cognition_messages = [ cognition_messages = [
{"role": "system", "content": load_prompt("cognition.system")}, {"role": "system", "content": await prompt_manager.render_prompt(cognition_prompt)},
{ {
"role": "user", "role": "user",
"content": f"以下是最近几轮对话记录,请分析其中用户的意图、认知状态和目的:\n\n{formatted}", "content": f"以下是最近几轮对话记录,请分析其中用户的意图、认知状态和目的:\n\n{formatted}",
@@ -329,8 +348,9 @@ class OpenAILLMService(BaseLLMService):
filtered_messages = [msg for msg in context_messages if msg.get("role") != "system"] filtered_messages = [msg for msg in context_messages if msg.get("role") != "system"]
formatted = format_chat_history(filtered_messages) formatted = format_chat_history(filtered_messages)
summarize_prompt = prompt_manager.get_prompt("maidairy_context_summarize")
summarize_messages = [ summarize_messages = [
{"role": "system", "content": load_prompt("context_summarize.system")}, {"role": "system", "content": await prompt_manager.render_prompt(summarize_prompt)},
{ {
"role": "user", "role": "user",
"content": f"请对以下对话内容进行总结,以便存入记忆系统:\n\n{formatted}", "content": f"请对以下对话内容进行总结,以便存入记忆系统:\n\n{formatted}",
@@ -368,7 +388,9 @@ class OpenAILLMService(BaseLLMService):
return [] return []
# 加载分类分析 prompt # 加载分类分析 prompt
prompt = load_prompt("knowledge_category.system", categories_summary=categories_summary) category_prompt = prompt_manager.get_prompt("maidairy_knowledge_category")
category_prompt.add_context("categories_summary", categories_summary)
prompt = await prompt_manager.render_prompt(category_prompt)
category_messages = [ category_messages = [
{"role": "system", "content": prompt}, {"role": "system", "content": prompt},
@@ -407,7 +429,9 @@ class OpenAILLMService(BaseLLMService):
return "" return ""
# 加载内容提取 prompt # 加载内容提取 prompt
prompt = load_prompt("knowledge_extract.system", category_name=category_name) extract_prompt = prompt_manager.get_prompt("maidairy_knowledge_extract")
extract_prompt.add_context("category_name", category_name)
prompt = await prompt_manager.render_prompt(extract_prompt)
extract_messages = [ extract_messages = [
{"role": "system", "content": prompt}, {"role": "system", "content": prompt},
@@ -454,10 +478,10 @@ class OpenAILLMService(BaseLLMService):
formatted = format_chat_history(recent_messages) formatted = format_chat_history(recent_messages)
# 加载需求分析 prompt # 加载需求分析 prompt
prompt = load_prompt("knowledge_retrieve.system", retrieve_prompt = prompt_manager.get_prompt("maidairy_knowledge_retrieve")
chat_context=formatted, retrieve_prompt.add_context("chat_context", formatted)
categories_summary=categories_summary retrieve_prompt.add_context("categories_summary", categories_summary)
) prompt = await prompt_manager.render_prompt(retrieve_prompt)
need_messages = [ need_messages = [
{"role": "system", "content": prompt}, {"role": "system", "content": prompt},

View File

@@ -2,8 +2,8 @@
MaiSaka - LLM 工具定义 MaiSaka - LLM 工具定义
所有 Tool Schema 集中管理 所有 Tool Schema 集中管理
注意所有 Prompt 模板已迁移至 prompts/ 目录使用 .prompt 文件存储 注意所有 Prompt 模板已迁移至主项目 prompts/ 目录使用 .prompt 文件存储
使用 prompt_loader.load_prompt() 加载模板 使用 prompt_manager.get_prompt("maidairy_xxx") 加载模板
""" """
# ──────────────────── 工具定义 ──────────────────── # ──────────────────── 工具定义 ────────────────────

View File

@@ -5,7 +5,7 @@ MaiSaka - Reply 回复生成器
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
from prompt_loader import load_prompt from src.prompt.prompt_manager import prompt_manager
from llm_service import BaseLLMService from llm_service import BaseLLMService
from llm_service.utils import format_chat_history from llm_service.utils import format_chat_history
@@ -60,8 +60,10 @@ class Replyer:
formatted_history = format_chat_history(filtered_history) formatted_history = format_chat_history(filtered_history)
# 构建回复消息 # 构建回复消息
replyer_prompt = prompt_manager.get_prompt("maidairy_replyer")
system_prompt = await prompt_manager.render_prompt(replyer_prompt)
messages = [ messages = [
{"role": "system", "content": load_prompt("replyer.system")}, {"role": "system", "content": system_prompt},
{ {
"role": "user", "role": "user",
"content": ( "content": (