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 - 程序入口
使用方法:
python main.py
python saka.py
环境变量 (可通过 .env 文件设置):
OPENAI_API_KEY - API 密钥
@@ -11,6 +11,16 @@ MaiSaka - 程序入口
"""
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 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,
WebUIConfig,
DatabaseConfig,
MaiSakaConfig,
)
from .model_configs import ModelInfo, ModelTaskConfig, APIProvider
from .config_base import ConfigBase, Field, AttributeData
@@ -127,6 +128,9 @@ class Config(ConfigBase):
database: DatabaseConfig = Field(default_factory=DatabaseConfig)
"""数据库配置类"""
maisaka: MaiSakaConfig = Field(default_factory=MaiSakaConfig)
"""MaiSaka对话系统配置类"""
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 模块
情绪感知分析分析用户的情绪状态和言语态度
注意EQ_SYSTEM_PROMPT 已迁移至 prompts/emotion.system.prompt
使用 prompt_loader.load_prompt("emotion.system") 加载
注意emotion.prompt 已迁移至主项目 prompts/ 目录
使用 prompt_manager.get_prompt("maidairy_emotion") 加载
"""
from typing import List, Optional

View File

@@ -8,13 +8,29 @@ from typing import Callable, List, Optional
from openai import AsyncOpenAI
import asyncio
from .base import BaseLLMService, ChatResponse, ModelInfo, ToolCall
from .prompts import get_enabled_chat_tools
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
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):
"""
基于 OpenAI 兼容 API LLM 服务实现
@@ -81,7 +97,7 @@ class OpenAILLMService(BaseLLMService):
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:
self._chat_system_prompt = chat_system_prompt
@@ -239,8 +255,9 @@ class OpenAILLMService(BaseLLMService):
if msg.get("_type") != "perception" and msg.get("role") != "system"
]
formatted = format_chat_history(filtered_history)
timing_prompt = prompt_manager.get_prompt("maidairy_timing")
timing_messages = [
{"role": "system", "content": load_prompt("timing.system")},
{"role": "system", "content": await prompt_manager.render_prompt(timing_prompt)},
{
"role": "user",
"content": (
@@ -272,8 +289,9 @@ class OpenAILLMService(BaseLLMService):
# 使用情商模块专用格式化函数:只包含用户回复、助手思考、助手说
formatted = format_chat_history_for_eq(recent_messages)
emotion_prompt = prompt_manager.get_prompt("maidairy_emotion")
eq_messages = [
{"role": "system", "content": load_prompt("emotion.system")},
{"role": "system", "content": await prompt_manager.render_prompt(emotion_prompt)},
{
"role": "user",
"content": f"以下是最近几轮对话记录,请分析其中用户的情绪状态和言语态度:\n\n{formatted}",
@@ -302,8 +320,9 @@ class OpenAILLMService(BaseLLMService):
# 使用情商模块专用格式化函数:只包含用户回复、助手思考、助手说
formatted = format_chat_history_for_eq(recent_messages)
cognition_prompt = prompt_manager.get_prompt("maidairy_cognition")
cognition_messages = [
{"role": "system", "content": load_prompt("cognition.system")},
{"role": "system", "content": await prompt_manager.render_prompt(cognition_prompt)},
{
"role": "user",
"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"]
formatted = format_chat_history(filtered_messages)
summarize_prompt = prompt_manager.get_prompt("maidairy_context_summarize")
summarize_messages = [
{"role": "system", "content": load_prompt("context_summarize.system")},
{"role": "system", "content": await prompt_manager.render_prompt(summarize_prompt)},
{
"role": "user",
"content": f"请对以下对话内容进行总结,以便存入记忆系统:\n\n{formatted}",
@@ -368,7 +388,9 @@ class OpenAILLMService(BaseLLMService):
return []
# 加载分类分析 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 = [
{"role": "system", "content": prompt},
@@ -407,7 +429,9 @@ class OpenAILLMService(BaseLLMService):
return ""
# 加载内容提取 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 = [
{"role": "system", "content": prompt},
@@ -454,10 +478,10 @@ class OpenAILLMService(BaseLLMService):
formatted = format_chat_history(recent_messages)
# 加载需求分析 prompt
prompt = load_prompt("knowledge_retrieve.system",
chat_context=formatted,
categories_summary=categories_summary
)
retrieve_prompt = prompt_manager.get_prompt("maidairy_knowledge_retrieve")
retrieve_prompt.add_context("chat_context", formatted)
retrieve_prompt.add_context("categories_summary", categories_summary)
prompt = await prompt_manager.render_prompt(retrieve_prompt)
need_messages = [
{"role": "system", "content": prompt},

View File

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

View File

@@ -5,7 +5,7 @@ MaiSaka - Reply 回复生成器
from typing import Optional
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.utils import format_chat_history
@@ -60,8 +60,10 @@ class Replyer:
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 = [
{"role": "system", "content": load_prompt("replyer.system")},
{"role": "system", "content": system_prompt},
{
"role": "user",
"content": (