Ruff Format
This commit is contained in:
@@ -7,6 +7,7 @@ MaiSaka - 内置工具定义
|
||||
from typing import List, Dict, Any
|
||||
from src.llm_models.payload_content.tool_option import ToolOption, ToolParamType
|
||||
|
||||
|
||||
# 内置工具定义
|
||||
def create_builtin_tools() -> List[ToolOption]:
|
||||
"""创建内置工具列表"""
|
||||
@@ -17,57 +18,66 @@ def create_builtin_tools() -> List[ToolOption]:
|
||||
# say 工具
|
||||
say_builder = ToolOptionBuilder()
|
||||
say_builder.set_name("say")
|
||||
say_builder.set_description("对用户说话。你所有想让用户看到的正式发言都必须通过此工具输出。直接输出的文本会被视为你的内心思考,用户无法阅读。reason 参数描述你想要回复的方式、想法和内容,系统会根据你的想法和对话上下文生成具体的回复。")
|
||||
say_builder.set_description(
|
||||
"对用户说话。你所有想让用户看到的正式发言都必须通过此工具输出。直接输出的文本会被视为你的内心思考,用户无法阅读。reason 参数描述你想要回复的方式、想法和内容,系统会根据你的想法和对话上下文生成具体的回复。"
|
||||
)
|
||||
say_builder.add_param(
|
||||
name="reason",
|
||||
param_type=ToolParamType.STRING,
|
||||
description="描述你想要回复的方式、想法和内容。例如:'同意对方的看法,并分享自己的经历' 或 '礼貌地拒绝,表示现在不方便聊天'",
|
||||
required=True,
|
||||
enum_values=None
|
||||
enum_values=None,
|
||||
)
|
||||
tools.append(say_builder.build())
|
||||
|
||||
# wait 工具
|
||||
wait_builder = ToolOptionBuilder()
|
||||
wait_builder.set_name("wait")
|
||||
wait_builder.set_description("暂时结束你的发言,把话语权交给用户,等待对方说话。这就像现实对话中你说完一句话后停下来等对方回应。如果用户在等待期间说了话,你会通过工具返回结果收到内容。如果超时没有回复,你也会收到超时通知。")
|
||||
wait_builder.set_description(
|
||||
"暂时结束你的发言,把话语权交给用户,等待对方说话。这就像现实对话中你说完一句话后停下来等对方回应。如果用户在等待期间说了话,你会通过工具返回结果收到内容。如果超时没有回复,你也会收到超时通知。"
|
||||
)
|
||||
wait_builder.add_param(
|
||||
name="seconds",
|
||||
param_type=ToolParamType.INTEGER,
|
||||
description="等待的秒数。建议 3-10 秒。超过这个时间用户没有回复会显示超时提示。",
|
||||
required=True,
|
||||
enum_values=None
|
||||
enum_values=None,
|
||||
)
|
||||
tools.append(wait_builder.build())
|
||||
|
||||
# stop 工具
|
||||
stop_builder = ToolOptionBuilder()
|
||||
stop_builder.set_name("stop")
|
||||
stop_builder.set_description("结束当前对话循环,进入待机状态,直到用户下次输入新内容时再唤醒你。当对话自然结束、用户表示不想继续聊、或连续多次等待超时用户没有回复时使用。")
|
||||
stop_builder.set_description(
|
||||
"结束当前对话循环,进入待机状态,直到用户下次输入新内容时再唤醒你。当对话自然结束、用户表示不想继续聊、或连续多次等待超时用户没有回复时使用。"
|
||||
)
|
||||
tools.append(stop_builder.build())
|
||||
|
||||
# store_context 工具
|
||||
store_context_builder = ToolOptionBuilder()
|
||||
store_context_builder.set_name("store_context")
|
||||
store_context_builder.set_description("将指定范围的对话上下文存入记忆系统,然后从当前对话中移除这些内容。适合在对话上下文过长、话题转换、或遇到重要内容需要保存时使用。")
|
||||
store_context_builder.set_description(
|
||||
"将指定范围的对话上下文存入记忆系统,然后从当前对话中移除这些内容。适合在对话上下文过长、话题转换、或遇到重要内容需要保存时使用。"
|
||||
)
|
||||
store_context_builder.add_param(
|
||||
name="count",
|
||||
param_type=ToolParamType.INTEGER,
|
||||
description="要保存的消息条数(从最早的对话开始计数)。建议 5-20 条。",
|
||||
required=True,
|
||||
enum_values=None
|
||||
enum_values=None,
|
||||
)
|
||||
store_context_builder.add_param(
|
||||
name="reason",
|
||||
param_type=ToolParamType.STRING,
|
||||
description="保存原因,用于后续检索。例如:'讨论了用户的工作情况' 或 '用户分享了对电影的看法'",
|
||||
required=True,
|
||||
enum_values=None
|
||||
enum_values=None,
|
||||
)
|
||||
tools.append(store_context_builder.build())
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
# 为了兼容性,创建一个函数来将工具转换为 dict 格式(用于调试显示)
|
||||
def builtin_tools_as_dicts() -> List[Dict[str, Any]]:
|
||||
"""将内置工具转换为 dict 格式(用于调试)"""
|
||||
@@ -77,31 +87,23 @@ def builtin_tools_as_dicts() -> List[Dict[str, Any]]:
|
||||
"description": "对用户说话。你所有想让用户看到的正式发言都必须通过此工具输出。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reason": {"type": "string", "description": "回复的想法和内容"}
|
||||
},
|
||||
"required": ["reason"]
|
||||
}
|
||||
"properties": {"reason": {"type": "string", "description": "回复的想法和内容"}},
|
||||
"required": ["reason"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "wait",
|
||||
"description": "暂时结束发言,等待用户回应",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"seconds": {"type": "number", "description": "等待秒数"}
|
||||
},
|
||||
"required": ["seconds"]
|
||||
}
|
||||
"properties": {"seconds": {"type": "number", "description": "等待秒数"}},
|
||||
"required": ["seconds"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "stop",
|
||||
"description": "结束对话循环",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
"parameters": {"type": "object", "properties": {}, "required": []},
|
||||
},
|
||||
{
|
||||
"name": "store_context",
|
||||
@@ -110,17 +112,19 @@ def builtin_tools_as_dicts() -> List[Dict[str, Any]]:
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {"type": "number", "description": "保存的消息条数"},
|
||||
"reason": {"type": "string", "description": "保存原因"}
|
||||
"reason": {"type": "string", "description": "保存原因"},
|
||||
},
|
||||
"required": ["count", "reason"]
|
||||
}
|
||||
}
|
||||
"required": ["count", "reason"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# 导出工具创建函数和列表
|
||||
def get_builtin_tools() -> List[ToolOption]:
|
||||
"""获取内置工具列表"""
|
||||
return create_builtin_tools()
|
||||
|
||||
|
||||
# 为了向后兼容,也导出 dict 格式
|
||||
BUILTIN_TOOLS_DICTS = builtin_tools_as_dicts()
|
||||
|
||||
@@ -13,10 +13,17 @@ from rich.markdown import Markdown
|
||||
from rich.text import Text
|
||||
from rich import box
|
||||
|
||||
from config import console, ENABLE_EMOTION_MODULE, ENABLE_COGNITION_MODULE, ENABLE_TIMING_MODULE, ENABLE_KNOWLEDGE_MODULE, ENABLE_MCP
|
||||
from config import (
|
||||
console,
|
||||
ENABLE_EMOTION_MODULE,
|
||||
ENABLE_COGNITION_MODULE,
|
||||
ENABLE_TIMING_MODULE,
|
||||
ENABLE_KNOWLEDGE_MODULE,
|
||||
ENABLE_MCP,
|
||||
)
|
||||
from input_reader import InputReader
|
||||
from timing import build_timing_info
|
||||
from knowledge import store_knowledge_from_context, retrieve_relevant_knowledge, build_knowledge_summary
|
||||
from knowledge import store_knowledge_from_context, retrieve_relevant_knowledge
|
||||
from knowledge_store import get_knowledge_store
|
||||
from llm_service import MaiSakaLLMService, build_message, remove_last_perception
|
||||
from mcp_client import MCPManager
|
||||
@@ -64,11 +71,7 @@ class BufferCLI:
|
||||
def _init_llm(self):
|
||||
"""初始化 LLM 服务 - 使用主项目配置系统"""
|
||||
thinking_env = os.getenv("ENABLE_THINKING", "").strip().lower()
|
||||
enable_thinking: Optional[bool] = (
|
||||
True if thinking_env == "true"
|
||||
else False if thinking_env == "false"
|
||||
else None
|
||||
)
|
||||
enable_thinking: Optional[bool] = True if thinking_env == "true" else False if thinking_env == "false" else None
|
||||
|
||||
# MaiSakaLLMService 现在使用主项目的配置系统
|
||||
# 参数仅为兼容性保留,实际从 config_manager 读取配置
|
||||
@@ -210,7 +213,7 @@ class BufferCLI:
|
||||
to_compress,
|
||||
store_result_callback=lambda cat_id, cat_name, content: console.print(
|
||||
f"[muted] [OK] 存储了解信息: {cat_name}[/muted]"
|
||||
)
|
||||
),
|
||||
)
|
||||
if knowledge_count > 0:
|
||||
console.print(f"[success][OK] 了解模块: 存储{knowledge_count}条特征信息[/success]")
|
||||
@@ -272,10 +275,12 @@ class BufferCLI:
|
||||
self._chat_history = self.llm_service.build_chat_context(user_text)
|
||||
else:
|
||||
# 后续对话:追加用户消息到已有上下文
|
||||
self._chat_history.append({
|
||||
"role": "user",
|
||||
"content": user_text,
|
||||
})
|
||||
self._chat_history.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": user_text,
|
||||
}
|
||||
)
|
||||
|
||||
await self._run_llm_loop(self._chat_history)
|
||||
|
||||
@@ -436,16 +441,17 @@ class BufferCLI:
|
||||
|
||||
if perception_parts:
|
||||
# 添加感知消息(AI 的感知能力结果)
|
||||
chat_history.append(build_message(
|
||||
role="assistant",
|
||||
content="\n\n".join(perception_parts),
|
||||
msg_type="perception",
|
||||
))
|
||||
chat_history.append(
|
||||
build_message(
|
||||
role="assistant",
|
||||
content="\n\n".join(perception_parts),
|
||||
msg_type="perception",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# 上次没有调用工具,跳过模块分析
|
||||
console.print("[muted]ℹ️ 上次未调用工具,跳过模块分析[/muted]")
|
||||
|
||||
|
||||
# ── 调用 LLM ──
|
||||
with console.status("[info]💬 AI 正在思考...[/info]", spinner="dots"):
|
||||
try:
|
||||
@@ -540,7 +546,8 @@ class BufferCLI:
|
||||
async def _init_mcp(self):
|
||||
"""初始化 MCP 服务器连接,发现并注册外部工具。"""
|
||||
config_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "mcp_config.json",
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"mcp_config.json",
|
||||
)
|
||||
self._mcp_manager = await MCPManager.from_config(config_path)
|
||||
|
||||
|
||||
@@ -15,16 +15,20 @@ if str(_root) not in sys.path:
|
||||
|
||||
# ──────────────────── 从主配置读取 ────────────────────
|
||||
|
||||
|
||||
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()
|
||||
|
||||
# ──────────────────── 模块开关配置 ────────────────────
|
||||
|
||||
@@ -48,9 +48,7 @@ class DebugViewer:
|
||||
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
conn.connect(("127.0.0.1", self._port))
|
||||
self._conn = conn
|
||||
console.print(
|
||||
f"[success]✓ 调试窗口已启动[/success] [muted](port {self._port})[/muted]"
|
||||
)
|
||||
console.print(f"[success]✓ 调试窗口已启动[/success] [muted](port {self._port})[/muted]")
|
||||
return
|
||||
except ConnectionRefusedError:
|
||||
conn.close()
|
||||
|
||||
@@ -16,10 +16,10 @@ from rich import box
|
||||
console = Console()
|
||||
|
||||
ROLE_STYLES = {
|
||||
"system": ("📋", "bold blue"),
|
||||
"user": ("👤", "bold green"),
|
||||
"system": ("📋", "bold blue"),
|
||||
"user": ("👤", "bold green"),
|
||||
"assistant": ("🤖", "bold magenta"),
|
||||
"tool": ("🔧", "bold yellow"),
|
||||
"tool": ("🔧", "bold yellow"),
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,10 @@ def format_message(idx: int, msg: dict) -> str:
|
||||
|
||||
# 正文
|
||||
if content:
|
||||
display = content if len(content) <= 3000 else (
|
||||
content[:3000] + f"\n[dim]... (截断, 共 {len(content)} 字符)[/dim]"
|
||||
display = (
|
||||
content
|
||||
if len(content) <= 3000
|
||||
else (content[:3000] + f"\n[dim]... (截断, 共 {len(content)} 字符)[/dim]")
|
||||
)
|
||||
parts.append(display)
|
||||
|
||||
@@ -88,8 +90,7 @@ def main():
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold cyan]MaiSaka Debug Viewer[/bold cyan]\n"
|
||||
f"[dim]监听端口: {port} 等待主进程连接...[/dim]",
|
||||
f"[bold cyan]MaiSaka Debug Viewer[/bold cyan]\n[dim]监听端口: {port} 等待主进程连接...[/dim]",
|
||||
box=box.DOUBLE_EDGE,
|
||||
border_style="cyan",
|
||||
)
|
||||
@@ -131,8 +132,7 @@ def main():
|
||||
# ── 标题栏 ──
|
||||
console.print(f"\n{'═' * 90}")
|
||||
console.print(
|
||||
f"[bold yellow]#{call_count} {label}[/bold yellow] "
|
||||
f"[dim]({len(messages)} messages)[/dim]"
|
||||
f"[bold yellow]#{call_count} {label}[/bold yellow] [dim]({len(messages)} messages)[/dim]"
|
||||
)
|
||||
console.print(f"{'═' * 90}")
|
||||
|
||||
@@ -144,12 +144,8 @@ def main():
|
||||
|
||||
# ── tools 信息 ──
|
||||
if tools:
|
||||
tool_names = [
|
||||
t.get("function", {}).get("name", "?") for t in tools
|
||||
]
|
||||
console.print(
|
||||
f"\n[dim]可用工具: {', '.join(tool_names)}[/dim]"
|
||||
)
|
||||
tool_names = [t.get("function", {}).get("name", "?") for t in tools]
|
||||
console.print(f"\n[dim]可用工具: {', '.join(tool_names)}[/dim]")
|
||||
except Exception as e:
|
||||
console.print(f"\n[red]数据处理错误: {e}[/red]")
|
||||
console.print(f"[dim]Payload: {payload}[/dim]")
|
||||
@@ -161,8 +157,12 @@ def main():
|
||||
console.print("\n[bold cyan]📤 LLM 响应:[/bold cyan]")
|
||||
resp_content = response.get("content", "")
|
||||
if resp_content:
|
||||
display = resp_content if len(str(resp_content)) <= 3000 else (
|
||||
str(resp_content)[:3000] + f"\n[dim]... (截断, 共 {len(str(resp_content))} 字符)[/dim]"
|
||||
display = (
|
||||
resp_content
|
||||
if len(str(resp_content)) <= 3000
|
||||
else (
|
||||
str(resp_content)[:3000] + f"\n[dim]... (截断, 共 {len(str(resp_content))} 字符)[/dim]"
|
||||
)
|
||||
)
|
||||
console.print(Panel(display, border_style="cyan", padding=(0, 1)))
|
||||
resp_tool_calls = response.get("tool_calls", [])
|
||||
|
||||
@@ -3,7 +3,7 @@ MaiSaka - 了解模块
|
||||
负责从对话中提取和存储用户个人特征信息。
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from knowledge_store import get_knowledge_store, KNOWLEDGE_CATEGORIES
|
||||
|
||||
|
||||
@@ -100,9 +100,7 @@ async def store_knowledge_from_context(
|
||||
|
||||
try:
|
||||
# 第一步:分析涉及哪些分类
|
||||
category_ids = await llm_service.analyze_knowledge_categories(
|
||||
context_messages, categories_summary
|
||||
)
|
||||
category_ids = await llm_service.analyze_knowledge_categories(context_messages, categories_summary)
|
||||
|
||||
if not category_ids:
|
||||
return 0
|
||||
@@ -119,25 +117,19 @@ async def store_knowledge_from_context(
|
||||
if extracted_content:
|
||||
# 存储到了解列表
|
||||
success = store.add_knowledge(
|
||||
category_id=category_id,
|
||||
content=extracted_content,
|
||||
metadata={"source": "context_compression"}
|
||||
category_id=category_id, content=extracted_content, metadata={"source": "context_compression"}
|
||||
)
|
||||
if success:
|
||||
stored_count += 1
|
||||
if store_result_callback:
|
||||
store_result_callback(
|
||||
category_id,
|
||||
store.get_category_name(category_id),
|
||||
extracted_content
|
||||
)
|
||||
except Exception as e:
|
||||
store_result_callback(category_id, store.get_category_name(category_id), extracted_content)
|
||||
except Exception:
|
||||
# 单个分类失败不影响其他分类
|
||||
continue
|
||||
|
||||
return stored_count
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
@@ -165,9 +157,7 @@ async def retrieve_relevant_knowledge(
|
||||
|
||||
try:
|
||||
# 分析需要哪些分类
|
||||
category_ids = await llm_service.analyze_knowledge_need(
|
||||
chat_history, categories_summary
|
||||
)
|
||||
category_ids = await llm_service.analyze_knowledge_need(chat_history, categories_summary)
|
||||
|
||||
if not category_ids:
|
||||
return ""
|
||||
|
||||
@@ -45,9 +45,7 @@ class KnowledgeStore:
|
||||
|
||||
def __init__(self):
|
||||
"""初始化了解存储"""
|
||||
self._knowledge: Dict[str, List[Dict[str, Any]]] = {
|
||||
category_id: [] for category_id in KNOWLEDGE_CATEGORIES
|
||||
}
|
||||
self._knowledge: Dict[str, List[Dict[str, Any]]] = {category_id: [] for category_id in KNOWLEDGE_CATEGORIES}
|
||||
self._ensure_data_dir()
|
||||
self._load()
|
||||
|
||||
@@ -58,9 +56,7 @@ class KnowledgeStore:
|
||||
def _load(self):
|
||||
"""从文件加载了解数据"""
|
||||
if not KNOWLEDGE_FILE.exists():
|
||||
self._knowledge = {
|
||||
category_id: [] for category_id in KNOWLEDGE_CATEGORIES
|
||||
}
|
||||
self._knowledge = {category_id: [] for category_id in KNOWLEDGE_CATEGORIES}
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -73,9 +69,7 @@ class KnowledgeStore:
|
||||
self._knowledge = loaded
|
||||
except Exception as e:
|
||||
print(f"[warning]加载了解数据失败: {e}[/warning]")
|
||||
self._knowledge = {
|
||||
category_id: [] for category_id in KNOWLEDGE_CATEGORIES
|
||||
}
|
||||
self._knowledge = {category_id: [] for category_id in KNOWLEDGE_CATEGORIES}
|
||||
|
||||
def _save(self):
|
||||
"""保存了解数据到文件"""
|
||||
|
||||
@@ -4,7 +4,6 @@ MaiSaka LLM 服务 - 使用主项目 LLM 系统
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Literal
|
||||
|
||||
@@ -34,6 +33,7 @@ MSG_TYPE_FIELD = "_type"
|
||||
@dataclass
|
||||
class ToolCall:
|
||||
"""工具调用信息"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
arguments: dict
|
||||
@@ -42,6 +42,7 @@ class ToolCall:
|
||||
@dataclass
|
||||
class ChatResponse:
|
||||
"""LLM 对话循环单步响应"""
|
||||
|
||||
content: Optional[str]
|
||||
tool_calls: List[ToolCall]
|
||||
raw_message: dict # 可直接追加到对话历史的消息字典
|
||||
@@ -49,6 +50,7 @@ class ChatResponse:
|
||||
|
||||
# ──────────────────── 工具函数 ────────────────────
|
||||
|
||||
|
||||
def build_message(role: str, content: str, msg_type: MessageType = "user", **kwargs) -> dict:
|
||||
"""构建消息字典,包含消息类型标记。"""
|
||||
msg = {"role": role, "content": content, MSG_TYPE_FIELD: msg_type, **kwargs}
|
||||
@@ -93,23 +95,18 @@ class MaiSakaLLMService:
|
||||
except Exception:
|
||||
# 如果配置加载失败,使用默认配置
|
||||
from src.config.model_configs import ModelTaskConfig
|
||||
|
||||
self._model_configs = ModelTaskConfig()
|
||||
logger.warning("无法加载主项目模型配置,使用默认配置")
|
||||
|
||||
# 初始化 LLMRequest 实例(只使用 tool_use 和 replyer)
|
||||
self._llm_tool_use = LLMRequest(
|
||||
model_set=self._model_configs.tool_use,
|
||||
request_type="maisaka_tool_use"
|
||||
)
|
||||
self._llm_tool_use = LLMRequest(model_set=self._model_configs.tool_use, request_type="maisaka_tool_use")
|
||||
# 主对话也使用 tool_use 模型(因为需要工具调用支持)
|
||||
self._llm_chat = self._llm_tool_use
|
||||
# 分析模块也使用 tool_use 模型
|
||||
self._llm_utils = self._llm_tool_use
|
||||
# 回复生成使用 replyer 模型
|
||||
self._llm_replyer = LLMRequest(
|
||||
model_set=self._model_configs.replyer,
|
||||
request_type="maisaka_replyer"
|
||||
)
|
||||
self._llm_replyer = LLMRequest(model_set=self._model_configs.replyer, request_type="maisaka_replyer")
|
||||
|
||||
# 尝试修复数据库 schema(忽略错误)
|
||||
self._try_fix_database_schema()
|
||||
@@ -133,6 +130,7 @@ class MaiSakaLLMService:
|
||||
|
||||
chat_prompt.add_context("file_tools_section", tools_section if tools_section else "")
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
@@ -147,7 +145,9 @@ class MaiSakaLLMService:
|
||||
self._chat_system_prompt = chat_system_prompt
|
||||
|
||||
# 获取模型名称用于显示
|
||||
self._model_name = self._model_configs.tool_use.model_list[0] if self._model_configs.tool_use.model_list else "未配置"
|
||||
self._model_name = (
|
||||
self._model_configs.tool_use.model_list[0] if self._model_configs.tool_use.model_list else "未配置"
|
||||
)
|
||||
|
||||
# 加载子模块提示词
|
||||
self._emotion_prompt: Optional[str] = None
|
||||
@@ -157,21 +157,22 @@ class MaiSakaLLMService:
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
self._emotion_prompt = loop.run_until_complete(prompt_manager.render_prompt(
|
||||
prompt_manager.get_prompt("maidairy_emotion")
|
||||
))
|
||||
self._cognition_prompt = loop.run_until_complete(prompt_manager.render_prompt(
|
||||
prompt_manager.get_prompt("maidairy_cognition")
|
||||
))
|
||||
self._timing_prompt = loop.run_until_complete(prompt_manager.render_prompt(
|
||||
prompt_manager.get_prompt("maidairy_timing")
|
||||
))
|
||||
self._context_summarize_prompt = loop.run_until_complete(prompt_manager.render_prompt(
|
||||
prompt_manager.get_prompt("maidairy_context_summarize")
|
||||
))
|
||||
self._emotion_prompt = loop.run_until_complete(
|
||||
prompt_manager.render_prompt(prompt_manager.get_prompt("maidairy_emotion"))
|
||||
)
|
||||
self._cognition_prompt = loop.run_until_complete(
|
||||
prompt_manager.render_prompt(prompt_manager.get_prompt("maidairy_cognition"))
|
||||
)
|
||||
self._timing_prompt = loop.run_until_complete(
|
||||
prompt_manager.render_prompt(prompt_manager.get_prompt("maidairy_timing"))
|
||||
)
|
||||
self._context_summarize_prompt = loop.run_until_complete(
|
||||
prompt_manager.render_prompt(prompt_manager.get_prompt("maidairy_context_summarize"))
|
||||
)
|
||||
logger.info("成功加载 MaiSaka 子模块提示词")
|
||||
finally:
|
||||
loop.close()
|
||||
@@ -191,9 +192,7 @@ class MaiSakaLLMService:
|
||||
|
||||
if "model_api_provider_name" not in columns:
|
||||
# 添加缺失的列
|
||||
session.execute(text(
|
||||
"ALTER TABLE llm_usage ADD COLUMN model_api_provider_name VARCHAR(255)"
|
||||
))
|
||||
session.execute(text("ALTER TABLE llm_usage ADD COLUMN model_api_provider_name VARCHAR(255)"))
|
||||
session.commit()
|
||||
logger.info("数据库 schema 已修复:添加 model_api_provider_name 列")
|
||||
except Exception:
|
||||
@@ -205,7 +204,7 @@ class MaiSakaLLMService:
|
||||
self._extra_tools = list(tools)
|
||||
|
||||
@staticmethod
|
||||
def _tool_option_to_dict(tool: 'ToolOption') -> dict:
|
||||
def _tool_option_to_dict(tool: "ToolOption") -> dict:
|
||||
"""将 ToolOption 对象转换为主项目期望的 dict 格式
|
||||
|
||||
主项目的 _build_tool_options() 期望的格式:
|
||||
@@ -218,18 +217,8 @@ class MaiSakaLLMService:
|
||||
params = []
|
||||
if tool.params:
|
||||
for param in tool.params:
|
||||
params.append((
|
||||
param.name,
|
||||
param.param_type,
|
||||
param.description,
|
||||
param.required,
|
||||
param.enum_values
|
||||
))
|
||||
return {
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"parameters": params
|
||||
}
|
||||
params.append((param.name, param.param_type, param.description, param.required, param.enum_values))
|
||||
return {"name": tool.name, "description": tool.description, "parameters": params}
|
||||
|
||||
async def chat_loop_step(self, chat_history: List[dict]) -> ChatResponse:
|
||||
"""执行对话循环的一步 - 使用 tool_use 模型"""
|
||||
@@ -271,11 +260,13 @@ class MaiSakaLLMService:
|
||||
for tc in msg["tool_calls"]:
|
||||
tc_func = tc.get("function", {})
|
||||
# 主项目的 ToolCall: call_id, func_name, args
|
||||
tool_calls_list.append(ToolCallOption(
|
||||
call_id=tc.get("id", ""),
|
||||
func_name=tc_func.get("name", ""),
|
||||
args=json.loads(tc_func.get("arguments", "{}")) if tc_func.get("arguments") else {}
|
||||
))
|
||||
tool_calls_list.append(
|
||||
ToolCallOption(
|
||||
call_id=tc.get("id", ""),
|
||||
func_name=tc_func.get("name", ""),
|
||||
args=json.loads(tc_func.get("arguments", "{}")) if tc_func.get("arguments") else {},
|
||||
)
|
||||
)
|
||||
builder.set_tool_calls(tool_calls_list)
|
||||
elif role == "tool" and "tool_call_id" in msg:
|
||||
builder.add_tool_call(msg["tool_call_id"])
|
||||
@@ -290,15 +281,17 @@ class MaiSakaLLMService:
|
||||
|
||||
# 调用 LLM(使用带消息的接口)
|
||||
# 合并内置工具和额外工具(将 ToolOption 对象转换为 dict)
|
||||
all_tools = [self._tool_option_to_dict(t) for t in get_builtin_tools()] + (self._extra_tools if self._extra_tools else [])
|
||||
all_tools = [self._tool_option_to_dict(t) for t in get_builtin_tools()] + (
|
||||
self._extra_tools if self._extra_tools else []
|
||||
)
|
||||
|
||||
# 打印消息列表
|
||||
built_messages = message_factory(None)
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("MaiSaka LLM Request - chat_loop_step:")
|
||||
for msg in built_messages:
|
||||
print(f" {msg}")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
response, (reasoning, model, tool_calls) = await self._llm_chat.generate_response_with_message_async(
|
||||
message_factory=message_factory,
|
||||
@@ -312,15 +305,17 @@ class MaiSakaLLMService:
|
||||
if tool_calls:
|
||||
for tc in tool_calls:
|
||||
# 主项目的 ToolCall 有 call_id, func_name, args
|
||||
call_id = tc.call_id if hasattr(tc, 'call_id') else ""
|
||||
func_name = tc.func_name if hasattr(tc, 'func_name') else ""
|
||||
args = tc.args if hasattr(tc, 'args') else {}
|
||||
call_id = tc.call_id if hasattr(tc, "call_id") else ""
|
||||
func_name = tc.func_name if hasattr(tc, "func_name") else ""
|
||||
args = tc.args if hasattr(tc, "args") else {}
|
||||
|
||||
converted_tool_calls.append(ToolCall(
|
||||
id=call_id,
|
||||
name=func_name,
|
||||
arguments=args,
|
||||
))
|
||||
converted_tool_calls.append(
|
||||
ToolCall(
|
||||
id=call_id,
|
||||
name=func_name,
|
||||
arguments=args,
|
||||
)
|
||||
)
|
||||
|
||||
# 构建原始消息格式(MaiSaka 风格)
|
||||
raw_message = {"role": "assistant", "content": response}
|
||||
@@ -394,10 +389,10 @@ class MaiSakaLLMService:
|
||||
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("MaiSaka LLM Request - analyze_emotion:")
|
||||
print(f" {prompt}")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
response, _ = await self._llm_utils.generate_response_async(
|
||||
@@ -428,10 +423,10 @@ class MaiSakaLLMService:
|
||||
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("MaiSaka LLM Request - analyze_cognition:")
|
||||
print(f" {prompt}")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
response, _ = await self._llm_utils.generate_response_async(
|
||||
@@ -463,10 +458,10 @@ class MaiSakaLLMService:
|
||||
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("MaiSaka LLM Request - analyze_timing:")
|
||||
print(f" {prompt}")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
response, _ = await self._llm_utils.generate_response_async(
|
||||
@@ -498,10 +493,10 @@ class MaiSakaLLMService:
|
||||
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("MaiSaka LLM Request - summarize_context:")
|
||||
print(f" {prompt}")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
response, _ = await self._llm_utils.generate_response_async(
|
||||
@@ -529,8 +524,7 @@ class MaiSakaLLMService:
|
||||
|
||||
# 格式化对话历史
|
||||
filtered_history = [
|
||||
msg for msg in chat_history
|
||||
if msg.get("role") != "system" and msg.get("_type") != "perception"
|
||||
msg for msg in chat_history if msg.get("role") != "system" and msg.get("_type") != "perception"
|
||||
]
|
||||
formatted_history = format_chat_history(filtered_history)
|
||||
|
||||
@@ -542,18 +536,15 @@ class MaiSakaLLMService:
|
||||
system_prompt = "你是一个友好的 AI 助手,请根据用户的想法生成自然的回复。"
|
||||
|
||||
user_prompt = (
|
||||
f"当前时间:{current_time}\n\n"
|
||||
f"【聊天记录】\n{formatted_history}\n\n"
|
||||
f"【你的想法】\n{reason}\n\n"
|
||||
f"现在,你说:"
|
||||
f"当前时间:{current_time}\n\n【聊天记录】\n{formatted_history}\n\n【你的想法】\n{reason}\n\n现在,你说:"
|
||||
)
|
||||
|
||||
messages = f"System: {system_prompt}\n\nUser: {user_prompt}"
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("MaiSaka LLM Request - generate_reply:")
|
||||
print(f" {messages}")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
response, _ = await self._llm_replyer.generate_response_async(
|
||||
|
||||
@@ -95,9 +95,7 @@ def load_mcp_config(config_path: str = "mcp_config.json") -> list[MCPServerConfi
|
||||
)
|
||||
|
||||
if server.transport_type == "unknown":
|
||||
console.print(
|
||||
f"[warning]⚠️ MCP 服务器 '{name}' 缺少 command 或 url,已跳过[/warning]"
|
||||
)
|
||||
console.print(f"[warning]⚠️ MCP 服务器 '{name}' 缺少 command 或 url,已跳过[/warning]")
|
||||
continue
|
||||
|
||||
configs.append(server)
|
||||
|
||||
@@ -63,9 +63,7 @@ class MCPConnection:
|
||||
True 表示连接成功,False 表示失败。
|
||||
"""
|
||||
if not MCP_AVAILABLE:
|
||||
console.print(
|
||||
"[warning]⚠️ 未安装 mcp SDK,请运行: pip install mcp[/warning]"
|
||||
)
|
||||
console.print("[warning]⚠️ 未安装 mcp SDK,请运行: pip install mcp[/warning]")
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -76,15 +74,11 @@ class MCPConnection:
|
||||
elif self.config.transport_type == "sse":
|
||||
read_stream, write_stream = await self._connect_sse()
|
||||
else:
|
||||
console.print(
|
||||
f"[warning]MCP '{self.config.name}': 未知传输类型[/warning]"
|
||||
)
|
||||
console.print(f"[warning]MCP '{self.config.name}': 未知传输类型[/warning]")
|
||||
return False
|
||||
|
||||
# 创建并初始化 MCP 会话
|
||||
self.session = await self._exit_stack.enter_async_context(
|
||||
ClientSession(read_stream, write_stream)
|
||||
)
|
||||
self.session = await self._exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
||||
await self.session.initialize()
|
||||
|
||||
# 发现工具
|
||||
@@ -94,9 +88,7 @@ class MCPConnection:
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
console.print(
|
||||
f"[warning]⚠️ MCP 服务器 '{self.config.name}' 连接失败: {e}[/warning]"
|
||||
)
|
||||
console.print(f"[warning]⚠️ MCP 服务器 '{self.config.name}' 连接失败: {e}[/warning]")
|
||||
await self.close()
|
||||
return False
|
||||
|
||||
@@ -107,19 +99,13 @@ class MCPConnection:
|
||||
args=self.config.args,
|
||||
env=self.config.env,
|
||||
)
|
||||
return await self._exit_stack.enter_async_context(
|
||||
stdio_client(params)
|
||||
)
|
||||
return await self._exit_stack.enter_async_context(stdio_client(params))
|
||||
|
||||
async def _connect_sse(self):
|
||||
"""建立 SSE 传输连接。"""
|
||||
if not SSE_AVAILABLE:
|
||||
raise ImportError(
|
||||
"SSE 传输需要额外依赖,请运行: pip install mcp[sse]"
|
||||
)
|
||||
return await self._exit_stack.enter_async_context(
|
||||
sse_client(url=self.config.url, headers=self.config.headers)
|
||||
)
|
||||
raise ImportError("SSE 传输需要额外依赖,请运行: pip install mcp[sse]")
|
||||
return await self._exit_stack.enter_async_context(sse_client(url=self.config.url, headers=self.config.headers))
|
||||
|
||||
async def call_tool(self, tool_name: str, arguments: dict) -> str:
|
||||
"""
|
||||
|
||||
@@ -10,10 +10,16 @@ from .config import MCPServerConfig, load_mcp_config
|
||||
from .connection import MCPConnection, MCP_AVAILABLE
|
||||
|
||||
# 内置工具名称集合 —— MCP 工具不允许与这些名称冲突
|
||||
BUILTIN_TOOL_NAMES = frozenset({
|
||||
"say", "wait", "stop",
|
||||
"create_table", "list_tables", "view_table",
|
||||
})
|
||||
BUILTIN_TOOL_NAMES = frozenset(
|
||||
{
|
||||
"say",
|
||||
"wait",
|
||||
"stop",
|
||||
"create_table",
|
||||
"list_tables",
|
||||
"view_table",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MCPManager:
|
||||
@@ -35,7 +41,8 @@ class MCPManager:
|
||||
|
||||
@classmethod
|
||||
async def from_config(
|
||||
cls, config_path: str = "mcp_config.json",
|
||||
cls,
|
||||
config_path: str = "mcp_config.json",
|
||||
) -> Optional["MCPManager"]:
|
||||
"""
|
||||
从配置文件创建并初始化 MCPManager。
|
||||
@@ -51,10 +58,7 @@ class MCPManager:
|
||||
return None
|
||||
|
||||
if not MCP_AVAILABLE:
|
||||
console.print(
|
||||
"[warning]⚠️ 发现 MCP 配置但未安装 mcp SDK,"
|
||||
"请运行: pip install mcp[/warning]"
|
||||
)
|
||||
console.print("[warning]⚠️ 发现 MCP 配置但未安装 mcp SDK,请运行: pip install mcp[/warning]")
|
||||
return None
|
||||
|
||||
manager = cls()
|
||||
@@ -85,8 +89,7 @@ class MCPManager:
|
||||
|
||||
if tool_name in BUILTIN_TOOL_NAMES:
|
||||
console.print(
|
||||
f"[warning]⚠️ MCP 工具 '{tool_name}' "
|
||||
f"(来自 {cfg.name}) 与内置工具冲突,已跳过[/warning]"
|
||||
f"[warning]⚠️ MCP 工具 '{tool_name}' (来自 {cfg.name}) 与内置工具冲突,已跳过[/warning]"
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -102,8 +105,7 @@ class MCPManager:
|
||||
registered += 1
|
||||
|
||||
console.print(
|
||||
f"[success]✓ MCP 服务器 '{cfg.name}' 已连接[/success] "
|
||||
f"[muted]({registered} 个工具已注册)[/muted]"
|
||||
f"[success]✓ MCP 服务器 '{cfg.name}' 已连接[/success] [muted]({registered} 个工具已注册)[/muted]"
|
||||
)
|
||||
|
||||
# ──────── 工具发现 ────────
|
||||
@@ -134,17 +136,16 @@ class MCPManager:
|
||||
# 移除 $schema 字段(部分 MCP 服务器会带上,OpenAI 不接受)
|
||||
parameters.pop("$schema", None)
|
||||
|
||||
tools.append({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool.name,
|
||||
"description": (
|
||||
tool.description
|
||||
or f"MCP tool from {server_name}"
|
||||
),
|
||||
"parameters": parameters,
|
||||
},
|
||||
})
|
||||
tools.append(
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool.name,
|
||||
"description": (tool.description or f"MCP tool from {server_name}"),
|
||||
"parameters": parameters,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return tools
|
||||
|
||||
@@ -184,9 +185,9 @@ class MCPManager:
|
||||
parts: list[str] = []
|
||||
for server_name, conn in self._connections.items():
|
||||
tool_names = [
|
||||
t.name for t in conn.tools
|
||||
if t.name in self._tool_to_server
|
||||
and self._tool_to_server[t.name] == server_name
|
||||
t.name
|
||||
for t in conn.tools
|
||||
if t.name in self._tool_to_server and self._tool_to_server[t.name] == server_name
|
||||
]
|
||||
if tool_names:
|
||||
parts.append(f" • {server_name}: {', '.join(tool_names)}")
|
||||
|
||||
@@ -49,8 +49,7 @@ def build_timing_info(
|
||||
|
||||
if len(user_input_times) >= 2:
|
||||
intervals = [
|
||||
(user_input_times[i] - user_input_times[i - 1]).total_seconds()
|
||||
for i in range(1, len(user_input_times))
|
||||
(user_input_times[i] - user_input_times[i - 1]).total_seconds() for i in range(1, len(user_input_times))
|
||||
]
|
||||
avg_interval = sum(intervals) / len(intervals)
|
||||
parts.append(f"用户平均回复间隔: {int(avg_interval)}秒")
|
||||
|
||||
@@ -4,7 +4,6 @@ MaiSaka - 工具调用处理器
|
||||
"""
|
||||
|
||||
import json as _json
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
@@ -92,27 +91,33 @@ async def handle_say(tc, chat_history: list, ctx: ToolHandlerContext):
|
||||
)
|
||||
)
|
||||
# 生成的回复作为 tool 结果写入上下文
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"已向用户展示(实际输出):{reply}",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"已向用户展示(实际输出):{reply}",
|
||||
}
|
||||
)
|
||||
else:
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": "reason 内容为空,未展示",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": "reason 内容为空,未展示",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_stop(tc, chat_history: list):
|
||||
"""处理 stop 工具:结束对话循环。"""
|
||||
console.print("[accent]🔧 调用工具: stop()[/accent]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": "对话循环已停止,等待用户下次输入。",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": "对话循环已停止,等待用户下次输入。",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_wait(tc, chat_history: list, ctx: ToolHandlerContext) -> str:
|
||||
@@ -128,11 +133,13 @@ async def handle_wait(tc, chat_history: list, ctx: ToolHandlerContext) -> str:
|
||||
|
||||
tool_result = await _do_wait(seconds, ctx)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": tool_result,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": tool_result,
|
||||
}
|
||||
)
|
||||
return tool_result
|
||||
|
||||
|
||||
@@ -193,28 +200,32 @@ async def handle_mcp_tool(tc, chat_history: list, mcp_manager: "MCPManager"):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_unknown_tool(tc, chat_history: list):
|
||||
"""处理未知工具调用。"""
|
||||
console.print(f"[accent]🔧 调用工具: {tc.name}({tc.arguments})[/accent]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"未知工具: {tc.name}",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"未知工具: {tc.name}",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_write_file(tc, chat_history: list):
|
||||
"""处理 write_file 工具:在 mai_files 目录下写入文件。"""
|
||||
filename = tc.arguments.get("filename", "")
|
||||
content = tc.arguments.get("content", "")
|
||||
console.print(f"[accent]🔧 调用工具: write_file(\"{filename}\")[/accent]")
|
||||
console.print(f'[accent]🔧 调用工具: write_file("{filename}")[/accent]')
|
||||
|
||||
# 确保目录存在
|
||||
MAI_FILES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
@@ -242,25 +253,29 @@ async def handle_write_file(tc, chat_history: list):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"文件「{filename}」已成功写入,共 {file_size} 个字符。",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"文件「{filename}」已成功写入,共 {file_size} 个字符。",
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"写入文件失败: {e}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_read_file(tc, chat_history: list):
|
||||
"""处理 read_file 工具:读取 mai_files 目录下的文件。"""
|
||||
filename = tc.arguments.get("filename", "")
|
||||
console.print(f"[accent]🔧 调用工具: read_file(\"{filename}\")[/accent]")
|
||||
console.print(f'[accent]🔧 调用工具: read_file("{filename}")[/accent]')
|
||||
|
||||
# 构建完整文件路径
|
||||
file_path = MAI_FILES_DIR / filename
|
||||
@@ -269,21 +284,25 @@ async def handle_read_file(tc, chat_history: list):
|
||||
if not file_path.exists():
|
||||
error_msg = f"文件「{filename}」不存在。"
|
||||
console.print(f"[warning]{error_msg}[/warning]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
if not file_path.is_file():
|
||||
error_msg = f"「{filename}」不是一个文件。"
|
||||
console.print(f"[warning]{error_msg}[/warning]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
# 读取文件内容
|
||||
@@ -304,19 +323,23 @@ async def handle_read_file(tc, chat_history: list):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"文件「{filename}」内容:\n{file_content}",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"文件「{filename}」内容:\n{file_content}",
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"读取文件失败: {e}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_list_files(tc, chat_history: list):
|
||||
@@ -334,11 +357,13 @@ async def handle_list_files(tc, chat_history: list):
|
||||
# 获取相对路径
|
||||
rel_path = item.relative_to(MAI_FILES_DIR)
|
||||
stat = item.stat()
|
||||
files_info.append({
|
||||
"name": str(rel_path),
|
||||
"size": stat.st_size,
|
||||
"modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
})
|
||||
files_info.append(
|
||||
{
|
||||
"name": str(rel_path),
|
||||
"size": stat.st_size,
|
||||
"modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
)
|
||||
|
||||
if not files_info:
|
||||
result_text = "mai_files 目录为空,没有任何文件。"
|
||||
@@ -360,19 +385,23 @@ async def handle_list_files(tc, chat_history: list):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_text,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_text,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"获取文件列表失败: {e}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_store_context(tc, chat_history: list, ctx: ToolHandlerContext):
|
||||
@@ -385,16 +414,18 @@ async def handle_store_context(tc, chat_history: list, ctx: ToolHandlerContext):
|
||||
"""
|
||||
count = tc.arguments.get("count", 0)
|
||||
reason = tc.arguments.get("reason", "")
|
||||
console.print(f"[accent]🔧 调用工具: store_context(count={count}, reason=\"{reason}\")[/accent]")
|
||||
console.print(f'[accent]🔧 调用工具: store_context(count={count}, reason="{reason}")[/accent]')
|
||||
|
||||
if count <= 0:
|
||||
error_msg = "count 参数必须大于 0"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
# 计算实际消息数量(排除 role=tool 的工具返回消息)
|
||||
@@ -423,9 +454,7 @@ async def handle_store_context(tc, chat_history: list, ctx: ToolHandlerContext):
|
||||
if role == "assistant" and "tool_calls" in msg:
|
||||
# 检查这个消息是否包含当前的 tool_call(store_context 自己)
|
||||
# 如果包含,跳过不删除(否则会导致 tool 响应孤儿)
|
||||
contains_current_call = any(
|
||||
tc.get("id") == tc.id for tc in msg.get("tool_calls", [])
|
||||
)
|
||||
contains_current_call = any(tc.get("id") == tc.id for tc in msg.get("tool_calls", []))
|
||||
if contains_current_call:
|
||||
i += 1
|
||||
continue
|
||||
@@ -453,11 +482,13 @@ async def handle_store_context(tc, chat_history: list, ctx: ToolHandlerContext):
|
||||
|
||||
if not indices_to_remove:
|
||||
result_msg = "没有找到可存入记忆的消息"
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
# 收集要总结的消息(在删除前)
|
||||
@@ -516,38 +547,45 @@ async def handle_store_context(tc, chat_history: list, ctx: ToolHandlerContext):
|
||||
chat_history.pop(i)
|
||||
i -= 1
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_get_qq_chat_info(tc, chat_history: list):
|
||||
"""处理 get_qq_chat_info 工具:通过 HTTP 获取 QQ 聊天内容。"""
|
||||
chat = tc.arguments.get("chat", "")
|
||||
limit = tc.arguments.get("limit", 20)
|
||||
console.print(f"[accent]🔧 调用工具: get_qq_chat_info(\"{chat}\", limit={limit})[/accent]")
|
||||
console.print(f'[accent]🔧 调用工具: get_qq_chat_info("{chat}", limit={limit})[/accent]')
|
||||
|
||||
if not AIOHTTP_AVAILABLE:
|
||||
error_msg = "aiohttp 模块未安装,请运行: pip install aiohttp"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
from config import QQ_API_BASE_URL, QQ_API_KEY
|
||||
|
||||
if not QQ_API_BASE_URL:
|
||||
error_msg = "QQ_API_BASE_URL 未配置,请在 .env 中设置"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -577,55 +615,66 @@ async def handle_get_qq_chat_info(tc, chat_history: list):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": text if text.strip() else "暂无聊天记录",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": text if text.strip() else "暂无聊天记录",
|
||||
}
|
||||
)
|
||||
else:
|
||||
error_text = await response.text()
|
||||
error_msg = f"HTTP 请求失败 (状态码 {response.status}): {error_text}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"获取 QQ 聊天记录失败: {e}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_send_info(tc, chat_history: list):
|
||||
"""处理 send_info 工具:通过 HTTP 发送消息到 QQ。"""
|
||||
chat = tc.arguments.get("chat", "")
|
||||
message = tc.arguments.get("message", "")
|
||||
console.print(f"[accent]🔧 调用工具: send_info(\"{chat}\")[/accent]")
|
||||
console.print(f'[accent]🔧 调用工具: send_info("{chat}")[/accent]')
|
||||
|
||||
if not AIOHTTP_AVAILABLE:
|
||||
error_msg = "aiohttp 模块未安装,请运行: pip install aiohttp"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
from config import QQ_API_BASE_URL, QQ_API_KEY
|
||||
|
||||
if not QQ_API_BASE_URL:
|
||||
error_msg = "QQ_API_BASE_URL 未配置,请在 .env 中设置"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -654,27 +703,33 @@ async def handle_send_info(tc, chat_history: list):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"消息发送成功: {data.get('message', '发送成功')}",
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": f"消息发送成功: {data.get('message', '发送成功')}",
|
||||
}
|
||||
)
|
||||
else:
|
||||
error_msg = f"发送失败: {data.get('message', '未知错误')}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"发送消息失败: {e}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def handle_list_qq_chats(tc, chat_history: list):
|
||||
@@ -684,22 +739,27 @@ async def handle_list_qq_chats(tc, chat_history: list):
|
||||
if not AIOHTTP_AVAILABLE:
|
||||
error_msg = "aiohttp 模块未安装,请运行: pip install aiohttp"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
from config import QQ_API_BASE_URL, QQ_API_KEY
|
||||
|
||||
if not QQ_API_BASE_URL:
|
||||
error_msg = "QQ_API_BASE_URL 未配置,请在 .env 中设置"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -721,10 +781,12 @@ async def handle_list_qq_chats(tc, chat_history: list):
|
||||
|
||||
# 格式化聊天列表
|
||||
if chats:
|
||||
chat_list_text = "\n".join([
|
||||
f" • [{c.get('platform', 'qq')}] {c.get('name', '未知')} (chat: {c.get('chat', 'N/A')})"
|
||||
for c in chats
|
||||
])
|
||||
chat_list_text = "\n".join(
|
||||
[
|
||||
f" • [{c.get('platform', 'qq')}] {c.get('name', '未知')} (chat: {c.get('chat', 'N/A')})"
|
||||
for c in chats
|
||||
]
|
||||
)
|
||||
result_text = f"可用的聊天 (共 {len(chats)} 个):\n{chat_list_text}"
|
||||
else:
|
||||
result_text = "没有可用的聊天"
|
||||
@@ -738,27 +800,33 @@ async def handle_list_qq_chats(tc, chat_history: list):
|
||||
)
|
||||
)
|
||||
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_text,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": result_text,
|
||||
}
|
||||
)
|
||||
else:
|
||||
error_msg = f"获取失败: {data.get('message', '未知错误')}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"获取聊天列表失败: {e}"
|
||||
console.print(f"[error]{error_msg}[/error]")
|
||||
chat_history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
})
|
||||
chat_history.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": error_msg,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ──────────────────── 初始化 mai_files 目录 ────────────────────
|
||||
|
||||
Reference in New Issue
Block a user