str:
+ """
+ 移除非代码元素的颜色样式
+ Remove color styles from non-code elements
+
+ Args:
+ html (str): HTML内容
+
+ Returns:
+ str: 处理后的HTML内容
+ """
+ # 定义代码相关标记(元组比列表更高效)
+ code_markers = (
+ "font-family:'Courier New'",
+ " bool:
+ """检查参数是否有效(非空且非纯空白)"""
+ return param and param.strip()
+
+
+def _process_ui_output(ui_output_dict: Dict[str, Any]) -> List[Union[str, Image]]:
+ """
+ 处理UI输出内容,提取文本、图片和文件引用
+
+ Args:
+ ui_output_dict: UI返回的输出字典
+
+ Returns:
+ List[Union[str, Image]]: 处理后的内容列表
+ """
+ processed_content: List[Union[str, Image]] = []
+
+ if not (
+ ui_output_dict
+ and "content" in ui_output_dict
+ and isinstance(ui_output_dict["content"], list)
+ ):
+ return processed_content
+
+ for item in ui_output_dict.get("content", []):
+ if not isinstance(item, dict):
+ print(f"警告: 无效的内容项格式: {item}", file=sys.stderr)
+ continue
+
+ item_type = item.get("type")
+ if item_type == "text":
+ text_content = item.get("text", "")
+ if text_content:
+ processed_content.append(text_content)
+ elif item_type == "image":
+ _process_image_item(item, processed_content)
+ elif item_type == "file_reference":
+ _process_file_reference_item(item, processed_content)
+ else:
+ print(f"警告: 未知的内容项类型: {item_type}", file=sys.stderr)
+
+ return processed_content
+
+
+def _process_image_item(
+ item: Dict[str, Any], processed_content: List[Union[str, Image]]
+) -> None:
+ """处理图片项"""
+ base64_data = item.get("data")
+ mime_type = item.get("mimeType")
+ if base64_data and mime_type:
+ try:
+ image_format_str = mime_type.split("/")[-1].lower()
+ if image_format_str == "jpeg":
+ image_format_str = "jpg"
+
+ image_bytes = base64.b64decode(base64_data)
+ mcp_image = Image(data=image_bytes, format=image_format_str)
+ processed_content.append(mcp_image)
+ except Exception as e:
+ print(f"错误: 处理图像失败: {e}", file=sys.stderr)
+ processed_content.append(f"[图像处理失败: {mime_type or 'unknown type'}]")
+
+
+def _process_file_reference_item(
+ item: Dict[str, Any], processed_content: List[Union[str, Image]]
+) -> None:
+ """处理文件引用项"""
+ display_name = item.get("display_name", "")
+ file_path = item.get("path", "")
+ if display_name and file_path:
+ file_info = f"引用文件: {display_name} [路径: {file_path}]"
+ processed_content.append(file_info)
+
+
+def get_system_prompts():
+ """
+ 获取系统提示词(从配置读取,使用config_manager中的默认值)
+ Get system prompts (read from config, use defaults from config_manager)
+
+ Returns:
+ dict: 包含optimize和reinforce提示词的字典
+ """
+ try:
+ config = get_config()
+ optimizer_config = config.get("expression_optimizer", {})
+ return optimizer_config.get("prompts", {})
+ except Exception:
+ # 回退到config_manager中的默认配置
+ from .utils.config_manager import DEFAULT_CONFIG
+
+ return DEFAULT_CONFIG["expression_optimizer"]["prompts"]
+
+
+def format_prompt_for_mode(
+ original_text: str, mode: str, reinforcement_prompt: str = None
+) -> str:
+ """
+ 根据模式格式化提示词
+ Format prompt based on mode
+
+ Args:
+ original_text: 原始文本
+ mode: 优化模式
+ reinforcement_prompt: 强化指令(可选)
+
+ Returns:
+ str: 格式化后的提示词
+ """
+ if mode == "reinforce" and reinforcement_prompt:
+ return f"强化指令: '{reinforcement_prompt}'\n\n原始文本: '{original_text}'"
+ else:
+ return original_text
+
+
+print(f"Server.py 启动 - Python解释器路径: {sys.executable}")
+print(f"Server.py 当前工作目录: {os.getcwd()}")
+
+
+mcp = FastMCP("Interactive Feedback MCP", log_level="ERROR")
+
+
+def launch_feedback_ui(
+ summary: str, predefined_options_list: Optional[List[str]] = None
+) -> Dict[str, Any]:
+ """
+ Launches the feedback UI as a separate process using its command-line entry point.
+ Collects user input and returns it as a structured dictionary.
+ """
+ tmp_file_path = None
+ try:
+ # 创建输出文件
+ with tempfile.NamedTemporaryFile(
+ suffix=".json", delete=False, mode="w", encoding="utf-8"
+ ) as tmp:
+ tmp_file_path = tmp.name
+
+ options_str = (
+ "|||".join(predefined_options_list) if predefined_options_list else ""
+ )
+
+ # Build the argument list for the 'feedback-ui' command
+ args_list = [
+ "feedback-ui",
+ "--prompt",
+ summary,
+ "--output-file",
+ tmp_file_path,
+ "--predefined-options",
+ options_str,
+ ]
+
+ # Run the feedback-ui command
+ process_result = subprocess.run(
+ args_list,
+ check=False,
+ shell=False,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdin=subprocess.DEVNULL,
+ close_fds=(
+ os.name != "nt"
+ ), # close_fds is not supported on Windows when shell=False
+ text=True,
+ encoding="utf-8",
+ errors="replace",
+ )
+
+ if process_result.returncode != 0:
+ print(
+ f"错误: 启动反馈UI失败,返回码: {process_result.returncode}",
+ file=sys.stderr,
+ )
+ if process_result.stdout:
+ print(f"UI STDOUT:\n{process_result.stdout}", file=sys.stderr)
+ if process_result.stderr:
+ print(f"UI STDERR:\n{process_result.stderr}", file=sys.stderr)
+ raise Exception(f"启动反馈UI失败: {process_result.returncode}")
+
+ with open(tmp_file_path, "r", encoding="utf-8") as f:
+ ui_result_data = json.load(f)
+
+ return ui_result_data
+
+ except FileNotFoundError:
+ print("错误: 'feedback-ui' 命令未找到", file=sys.stderr)
+ print("请确保项目已在可编辑模式下安装 (pip install -e .)", file=sys.stderr)
+ raise
+ except Exception as e:
+ print(f"错误: launch_feedback_ui 异常: {e}", file=sys.stderr)
+ raise
+ finally:
+ # 清理临时文件
+ if tmp_file_path and os.path.exists(tmp_file_path):
+ try:
+ os.unlink(tmp_file_path)
+ except OSError as e_unlink:
+ print(
+ f"警告: 删除临时文件失败 '{tmp_file_path}': {e_unlink}",
+ file=sys.stderr,
+ )
+
+
+@mcp.tool()
+def interactive_feedback(
+ message: Optional[str] = Field(
+ default=None,
+ description="[SIMPLE mode] Concise question for user input (AI must display full response in chat first)",
+ ),
+ full_response: Optional[str] = Field(
+ default=None,
+ description="[FULL mode] AI's complete response content (AI must display this in chat first)",
+ ),
+ predefined_options: List[str] = Field(
+ default_factory=list, description="Predefined options for user selection"
+ ),
+) -> Tuple[Union[str, Image], ...]: # 返回字符串和/或 fastmcp.Image 对象的元组
+ """
+ Requests user input via GUI after AI displays complete response in chat.
+
+ USAGE FLOW:
+ 1. AI displays complete response in chat dialog
+ 2. AI calls this tool to collect user input
+ 3. Tool returns user feedback only
+
+ This tool collects user input, not for displaying AI responses.
+ AI responses must appear in chat dialog before calling this tool.
+
+ PARAMETER REQUIREMENTS:
+ - AI MUST provide BOTH 'message' and 'full_response' parameters
+ - Both parameters cannot be empty or whitespace-only
+ - MCP service will automatically select which content to display based on user's display_mode setting
+
+ USAGE PATTERN:
+
+ # Step 1: AI displays complete response in chat
+ # Step 2: AI calls tool with BOTH parameters
+ interactive_feedback(
+ message="你希望我实现这些更改吗?", # Required: concise question
+ full_response="我分析了你的代码,发现了3个问题...", # Required: complete response
+ predefined_options=["修复方案A", "修复方案B", "让我想想"]
+ )
+
+ Note: MCP service automatically selects appropriate content based on user's display mode configuration.
+ """
+
+ # 严格的双参数验证:AI必须同时提供两个有效参数
+ if not _is_valid_param(message) or not _is_valid_param(full_response):
+ return (ERROR_MESSAGES["missing_both_params"],)
+
+ # 获取配置(一次性读取,避免重复)
+ config = get_config()
+ display_mode = get_display_mode(config)
+
+ # 根据用户配置的显示模式选择要展示的内容
+ prompt_to_display = full_response if display_mode == "full" else message
+
+ # 解析最终选项 - 兼容性修复:将空列表转换为None以保持现有逻辑
+ ai_options_normalized = predefined_options if predefined_options else None
+ final_options = resolve_final_options(
+ ai_options=ai_options_normalized, text=prompt_to_display, config=config
+ )
+
+ # 转换为UI需要的格式(final_options已经是字符串列表,无需转换)
+ options_list_for_ui = final_options if final_options else None
+
+ # 启动UI并获取用户输入
+ ui_output_dict = launch_feedback_ui(prompt_to_display, options_list_for_ui)
+
+ # 处理UI输出内容
+ processed_mcp_content = _process_ui_output(ui_output_dict)
+
+ if not processed_mcp_content:
+ return (ERROR_MESSAGES["no_user_feedback"],)
+
+ return tuple(processed_mcp_content)
+
+
+def _optimize_user_input_internal(
+ original_text: str,
+ mode: str,
+ reinforcement_prompt: Optional[str] = None,
+) -> str:
+ """
+ 内部优化函数,供GUI和MCP工具共同使用
+ Internal optimization function for both GUI and MCP tool usage
+
+ Args:
+ original_text: 用户的原始输入文本
+ mode: 优化模式 ('optimize' 或 'reinforce')
+ reinforcement_prompt: 在 'reinforce' 模式下用户的自定义指令
+
+ Returns:
+ str: 优化后的文本或错误信息
+ """
+ try:
+ # 导入LLM模块
+ from .llm.factory import get_llm_provider
+ from .llm.performance_manager import get_optimization_manager
+
+ # 获取配置
+ config = get_config().get("expression_optimizer", {})
+
+ # 获取LLM provider
+ provider, status_message = get_llm_provider(config)
+
+ if not provider:
+ return f"[优化功能不可用] {status_message}"
+
+ # 获取系统提示词
+ system_prompts = get_system_prompts()
+
+ # 验证模式和参数
+ if mode == "optimize":
+ system_prompt = system_prompts["optimize"]
+ elif mode == "reinforce":
+ if not reinforcement_prompt:
+ return "[错误] 'reinforce' 模式需要提供强化指令"
+ system_prompt = system_prompts["reinforce"]
+ else:
+ return f"[错误] 无效的优化模式: {mode}。支持的模式: 'optimize', 'reinforce'"
+
+ # 简化逻辑:默认使用性能管理器(包含缓存功能)
+ manager = get_optimization_manager(config)
+
+ result = manager.optimize_with_cache(
+ provider=provider,
+ text=original_text,
+ mode=mode,
+ system_prompt=system_prompt,
+ reinforcement=reinforcement_prompt or "",
+ )
+
+ # 检查是否是错误信息
+ if result.startswith("[ERROR"):
+ return f"[优化失败] {result}"
+
+ return result
+
+ except ImportError as e:
+ return f"[配置错误] LLM模块导入失败: {e}"
+ except Exception as e:
+ return f"[系统错误] 优化过程中发生异常: {e}"
+
+
+@mcp.tool()
+def optimize_user_input(
+ original_text: str = Field(description="用户的原始输入文本"),
+ mode: str = Field(description="优化模式: 'optimize' 或 'reinforce'"),
+ reinforcement_prompt: Optional[str] = Field(
+ default=None, description="在 'reinforce' 模式下用户的自定义指令"
+ ),
+) -> str:
+ """
+ 使用配置的 LLM API 来优化或强化用户输入的文本。
+
+ 此功能可以帮助用户将口语化的、可能存在歧义的输入,转化为更结构化、
+ 更清晰、更便于 AI 模型理解的文本。
+
+ Args:
+ original_text: 用户的原始输入文本
+ mode: 优化模式
+ - 'optimize': 一键优化,使用预设的通用优化指令
+ - 'reinforce': 提示词强化,使用用户自定义的强化指令
+ reinforcement_prompt: 在 'reinforce' 模式下用户的自定义指令
+
+ Returns:
+ str: 优化后的文本或错误信息
+ """
+ return _optimize_user_input_internal(original_text, mode, reinforcement_prompt)
+
+
+def main():
+ """Main function to run the MCP server."""
+ mcp.run(transport="stdio")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/interactive_feedback_server/cli.py.backup b/src/interactive_feedback_server/cli.py.backup
new file mode 100644
index 0000000..0a1f6c4
--- /dev/null
+++ b/src/interactive_feedback_server/cli.py.backup
@@ -0,0 +1,395 @@
+# Interactive Feedback MCP
+# Developed by Fábio Ferreira (https://x.com/fabiomlferreira)
+# Inspired by/related to dotcursorrules.com (https://dotcursorrules.com/)
+# Enhanced by pawa (https://github.com/pawaovo) with ideas from https://github.com/noopstudios/interactive-feedback-mcp
+import os
+import sys
+import json
+import tempfile
+import subprocess
+import base64
+
+# from typing import Annotated # Annotated 未在此文件中直接使用 (Annotated not directly used in this file)
+from typing import (
+ Dict,
+ List,
+ Any,
+ Optional,
+ Tuple,
+ Union,
+) # 简化导入 (Simplified imports)
+
+from fastmcp import FastMCP, Image
+from pydantic import (
+ Field,
+) # Field 由 FastMCP 内部使用 (Field is used internally by FastMCP)
+
+from .utils import get_config, resolve_final_options, get_display_mode
+
+# 错误消息常量
+ERROR_MESSAGES = {
+ "missing_both_params": "[错误] AI必须同时提供message和full_response两个参数,不能为空",
+ "no_user_feedback": "[用户未提供反馈]",
+}
+
+
+def _is_valid_param(param: Optional[str]) -> bool:
+ """检查参数是否有效(非空且非纯空白)"""
+ return param and param.strip()
+
+
+def _process_ui_output(ui_output_dict: Dict[str, Any]) -> List[Union[str, Image]]:
+ """
+ 处理UI输出内容,提取文本、图片和文件引用
+
+ Args:
+ ui_output_dict: UI返回的输出字典
+
+ Returns:
+ List[Union[str, Image]]: 处理后的内容列表
+ """
+ processed_content: List[Union[str, Image]] = []
+
+ if not (
+ ui_output_dict
+ and "content" in ui_output_dict
+ and isinstance(ui_output_dict["content"], list)
+ ):
+ return processed_content
+
+ for item in ui_output_dict.get("content", []):
+ if not isinstance(item, dict):
+ print(f"警告: 无效的内容项格式: {item}", file=sys.stderr)
+ continue
+
+ item_type = item.get("type")
+ if item_type == "text":
+ text_content = item.get("text", "")
+ if text_content:
+ processed_content.append(text_content)
+ elif item_type == "image":
+ _process_image_item(item, processed_content)
+ elif item_type == "file_reference":
+ _process_file_reference_item(item, processed_content)
+ else:
+ print(f"警告: 未知的内容项类型: {item_type}", file=sys.stderr)
+
+ return processed_content
+
+
+def _process_image_item(
+ item: Dict[str, Any], processed_content: List[Union[str, Image]]
+) -> None:
+ """处理图片项"""
+ base64_data = item.get("data")
+ mime_type = item.get("mimeType")
+ if base64_data and mime_type:
+ try:
+ image_format_str = mime_type.split("/")[-1].lower()
+ if image_format_str == "jpeg":
+ image_format_str = "jpg"
+
+ image_bytes = base64.b64decode(base64_data)
+ mcp_image = Image(data=image_bytes, format=image_format_str)
+ processed_content.append(mcp_image)
+ except Exception as e:
+ print(f"错误: 处理图像失败: {e}", file=sys.stderr)
+ processed_content.append(f"[图像处理失败: {mime_type or 'unknown type'}]")
+
+
+def _process_file_reference_item(
+ item: Dict[str, Any], processed_content: List[Union[str, Image]]
+) -> None:
+ """处理文件引用项"""
+ display_name = item.get("display_name", "")
+ file_path = item.get("path", "")
+ if display_name and file_path:
+ file_info = f"引用文件: {display_name} [路径: {file_path}]"
+ processed_content.append(file_info)
+
+
+def get_system_prompts():
+ """
+ 获取系统提示词(从配置读取,使用config_manager中的默认值)
+ Get system prompts (read from config, use defaults from config_manager)
+
+ Returns:
+ dict: 包含optimize和reinforce提示词的字典
+ """
+ try:
+ config = get_config()
+ optimizer_config = config.get("expression_optimizer", {})
+ return optimizer_config.get("prompts", {})
+ except Exception:
+ # 回退到config_manager中的默认配置
+ from .utils.config_manager import DEFAULT_CONFIG
+
+ return DEFAULT_CONFIG["expression_optimizer"]["prompts"]
+
+
+def format_prompt_for_mode(
+ original_text: str, mode: str, reinforcement_prompt: str = None
+) -> str:
+ """
+ 根据模式格式化提示词
+ Format prompt based on mode
+
+ Args:
+ original_text: 原始文本
+ mode: 优化模式
+ reinforcement_prompt: 强化指令(可选)
+
+ Returns:
+ str: 格式化后的提示词
+ """
+ if mode == "reinforce" and reinforcement_prompt:
+ return f"强化指令: '{reinforcement_prompt}'\n\n原始文本: '{original_text}'"
+ else:
+ return original_text
+
+
+print(f"Server.py 启动 - Python解释器路径: {sys.executable}")
+print(f"Server.py 当前工作目录: {os.getcwd()}")
+
+
+mcp = FastMCP("Interactive Feedback MCP", log_level="ERROR")
+
+
+def launch_feedback_ui(
+ summary: str, predefined_options_list: Optional[List[str]] = None
+) -> Dict[str, Any]:
+ """
+ Launches the feedback UI as a separate process using its command-line entry point.
+ Collects user input and returns it as a structured dictionary.
+ """
+ tmp_file_path = None
+ try:
+ # 创建输出文件
+ with tempfile.NamedTemporaryFile(
+ suffix=".json", delete=False, mode="w", encoding="utf-8"
+ ) as tmp:
+ tmp_file_path = tmp.name
+
+ options_str = (
+ "|||".join(predefined_options_list) if predefined_options_list else ""
+ )
+
+ # Build the argument list for the 'feedback-ui' command
+ args_list = [
+ "feedback-ui",
+ "--prompt",
+ summary,
+ "--output-file",
+ tmp_file_path,
+ "--predefined-options",
+ options_str,
+ ]
+
+ # Run the feedback-ui command
+ process_result = subprocess.run(
+ args_list,
+ check=False,
+ shell=False,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdin=subprocess.DEVNULL,
+ close_fds=(
+ os.name != "nt"
+ ), # close_fds is not supported on Windows when shell=False
+ text=True,
+ encoding="utf-8",
+ errors="replace",
+ )
+
+ if process_result.returncode != 0:
+ print(
+ f"错误: 启动反馈UI失败,返回码: {process_result.returncode}",
+ file=sys.stderr,
+ )
+ if process_result.stdout:
+ print(f"UI STDOUT:\n{process_result.stdout}", file=sys.stderr)
+ if process_result.stderr:
+ print(f"UI STDERR:\n{process_result.stderr}", file=sys.stderr)
+ raise Exception(f"启动反馈UI失败: {process_result.returncode}")
+
+ with open(tmp_file_path, "r", encoding="utf-8") as f:
+ ui_result_data = json.load(f)
+
+ return ui_result_data
+
+ except FileNotFoundError:
+ print("错误: 'feedback-ui' 命令未找到", file=sys.stderr)
+ print("请确保项目已在可编辑模式下安装 (pip install -e .)", file=sys.stderr)
+ raise
+ except Exception as e:
+ print(f"错误: launch_feedback_ui 异常: {e}", file=sys.stderr)
+ raise
+ finally:
+ # 清理临时文件
+ if tmp_file_path and os.path.exists(tmp_file_path):
+ try:
+ os.unlink(tmp_file_path)
+ except OSError as e_unlink:
+ print(
+ f"警告: 删除临时文件失败 '{tmp_file_path}': {e_unlink}",
+ file=sys.stderr,
+ )
+
+
+@mcp.tool()
+def interactive_feedback(
+ message: Optional[str] = Field(
+ default=None,
+ description="[SIMPLE mode] Concise question for user input (AI must display full response in chat first)",
+ ),
+ full_response: Optional[str] = Field(
+ default=None,
+ description="[FULL mode] AI's complete response content (AI must display this in chat first)",
+ ),
+ predefined_options: List[str] = Field(
+ default_factory=list,
+ description="Predefined options for user selection"
+ ),
+) -> Tuple[Union[str, Image], ...]: # 返回字符串和/或 fastmcp.Image 对象的元组
+ """
+ Requests user input via GUI after AI displays complete response in chat.
+
+ USAGE FLOW:
+ 1. AI displays complete response in chat dialog
+ 2. AI calls this tool to collect user input
+ 3. Tool returns user feedback only
+
+ This tool collects user input, not for displaying AI responses.
+ AI responses must appear in chat dialog before calling this tool.
+
+ PARAMETER REQUIREMENTS:
+ - AI MUST provide BOTH 'message' and 'full_response' parameters
+ - Both parameters cannot be empty or whitespace-only
+ - MCP service will automatically select which content to display based on user's display_mode setting
+
+ USAGE PATTERN:
+
+ # Step 1: AI displays complete response in chat
+ # Step 2: AI calls tool with BOTH parameters
+ interactive_feedback(
+ message="你希望我实现这些更改吗?", # Required: concise question
+ full_response="我分析了你的代码,发现了3个问题...", # Required: complete response
+ predefined_options=["修复方案A", "修复方案B", "让我想想"]
+ )
+
+ Note: MCP service automatically selects appropriate content based on user's display mode configuration.
+ """
+
+ # 严格的双参数验证:AI必须同时提供两个有效参数
+ if not _is_valid_param(message) or not _is_valid_param(full_response):
+ return (ERROR_MESSAGES["missing_both_params"],)
+
+ # 获取配置(一次性读取,避免重复)
+ config = get_config()
+ display_mode = get_display_mode(config)
+
+ # 根据用户配置的显示模式选择要展示的内容
+ prompt_to_display = full_response if display_mode == "full" else message
+
+ # 解析最终选项 - 兼容性修复:将空列表转换为None以保持现有逻辑
+ ai_options_normalized = predefined_options if predefined_options else None
+ final_options = resolve_final_options(
+ ai_options=ai_options_normalized, text=prompt_to_display, config=config
+ )
+
+ # 转换为UI需要的格式(final_options已经是字符串列表,无需转换)
+ options_list_for_ui = final_options if final_options else None
+
+ # 启动UI并获取用户输入
+ ui_output_dict = launch_feedback_ui(prompt_to_display, options_list_for_ui)
+
+ # 处理UI输出内容
+ processed_mcp_content = _process_ui_output(ui_output_dict)
+
+ if not processed_mcp_content:
+ return (ERROR_MESSAGES["no_user_feedback"],)
+
+ return tuple(processed_mcp_content)
+
+
+@mcp.tool()
+def optimize_user_input(
+ original_text: str = Field(description="用户的原始输入文本"),
+ mode: str = Field(description="优化模式: 'optimize' 或 'reinforce'"),
+ reinforcement_prompt: Optional[str] = Field(
+ default=None, description="在 'reinforce' 模式下用户的自定义指令"
+ ),
+) -> str:
+ """
+ 使用配置的 LLM API 来优化或强化用户输入的文本。
+
+ 此功能可以帮助用户将口语化的、可能存在歧义的输入,转化为更结构化、
+ 更清晰、更便于 AI 模型理解的文本。
+
+ Args:
+ original_text: 用户的原始输入文本
+ mode: 优化模式
+ - 'optimize': 一键优化,使用预设的通用优化指令
+ - 'reinforce': 提示词强化,使用用户自定义的强化指令
+ reinforcement_prompt: 在 'reinforce' 模式下用户的自定义指令
+
+ Returns:
+ str: 优化后的文本或错误信息
+ """
+ try:
+ # 导入LLM模块
+ from .llm.factory import get_llm_provider
+ from .llm.performance_manager import get_optimization_manager
+
+ # 获取配置
+ config = get_config().get("expression_optimizer", {})
+
+ # 获取LLM provider
+ provider, status_message = get_llm_provider(config)
+
+ if not provider:
+ return f"[优化功能不可用] {status_message}"
+
+ # 获取系统提示词
+ system_prompts = get_system_prompts()
+
+ # 验证模式和参数
+ if mode == "optimize":
+ system_prompt = system_prompts["optimize"]
+ elif mode == "reinforce":
+ if not reinforcement_prompt:
+ return "[错误] 'reinforce' 模式需要提供强化指令"
+ system_prompt = system_prompts["reinforce"]
+ else:
+ return f"[错误] 无效的优化模式: {mode}。支持的模式: 'optimize', 'reinforce'"
+
+ # 简化逻辑:默认使用性能管理器(包含缓存功能)
+ manager = get_optimization_manager(config)
+
+ result = manager.optimize_with_cache(
+ provider=provider,
+ text=original_text,
+ mode=mode,
+ system_prompt=system_prompt,
+ reinforcement=reinforcement_prompt or "",
+ )
+
+ # 检查是否是错误信息
+ if result.startswith("[ERROR"):
+ return f"[优化失败] {result}"
+
+ return result
+
+ except ImportError as e:
+ return f"[配置错误] LLM模块导入失败: {e}"
+ except Exception as e:
+ return f"[系统错误] 优化过程中发生异常: {e}"
+
+
+def main():
+ """Main function to run the MCP server."""
+ mcp.run(transport="stdio")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/interactive_feedback_server/core/__init__.py b/src/interactive_feedback_server/core/__init__.py
new file mode 100644
index 0000000..02b3f22
--- /dev/null
+++ b/src/interactive_feedback_server/core/__init__.py
@@ -0,0 +1,50 @@
+# interactive_feedback_server/core/__init__.py
+
+"""
+核心模块 - 优化版本
+Core Module - Optimized Version
+
+提供统一的核心功能,消除重复代码和模式。
+Provides unified core functionality, eliminates duplicate code and patterns.
+"""
+
+from .singleton_manager import (
+ SingletonManager,
+ SingletonBase,
+ register_singleton,
+ get_singleton,
+ clear_singleton,
+ get_singleton_manager,
+)
+
+from .stats_collector import (
+ UnifiedStatsCollector,
+ StatEntry,
+ get_stats_collector,
+ increment_stat,
+ set_stat_gauge,
+ record_stat_value,
+ get_all_stats,
+)
+
+# 已删除未使用的统一配置加载器模块
+
+__all__ = [
+ # 单例管理
+ "SingletonManager",
+ "SingletonBase",
+ "register_singleton",
+ "get_singleton",
+ "clear_singleton",
+ "get_singleton_manager",
+ # 统计收集
+ "UnifiedStatsCollector",
+ "StatEntry",
+ "get_stats_collector",
+ "increment_stat",
+ "set_stat_gauge",
+ "record_stat_value",
+ "get_all_stats",
+]
+
+__version__ = "3.3.0"
diff --git a/src/interactive_feedback_server/core/singleton_manager.py b/src/interactive_feedback_server/core/singleton_manager.py
new file mode 100644
index 0000000..cffc4a6
--- /dev/null
+++ b/src/interactive_feedback_server/core/singleton_manager.py
@@ -0,0 +1,174 @@
+# interactive_feedback_server/core/singleton_manager.py
+
+"""
+统一的单例管理器 - 优化版本
+Unified Singleton Manager - Optimized Version
+
+消除重复的全局实例模式,提供统一的单例管理。
+Eliminates duplicate global instance patterns, provides unified singleton management.
+"""
+
+import threading
+from typing import Dict, Any, Type, TypeVar, Callable, Optional
+from functools import wraps
+
+T = TypeVar("T")
+
+
+class SingletonManager:
+ """
+ 单例管理器
+ Singleton Manager
+
+ 统一管理所有单例实例,避免重复的全局变量模式
+ Unified management of all singleton instances, avoiding duplicate global variable patterns
+ """
+
+ def __init__(self):
+ """初始化单例管理器"""
+ self._instances: Dict[str, Any] = {}
+ self._factories: Dict[str, Callable] = {}
+ self._lock = threading.RLock()
+
+ def register_factory(self, name: str, factory: Callable[[], T]) -> None:
+ """
+ 注册单例工厂函数
+ Register singleton factory function
+
+ Args:
+ name: 单例名称
+ factory: 工厂函数
+ """
+ with self._lock:
+ self._factories[name] = factory
+
+ def get_instance(self, name: str) -> Any:
+ """
+ 获取单例实例
+ Get singleton instance
+
+ Args:
+ name: 单例名称
+
+ Returns:
+ Any: 单例实例
+ """
+ with self._lock:
+ if name not in self._instances:
+ if name not in self._factories:
+ raise ValueError(f"未注册的单例: {name}")
+
+ self._instances[name] = self._factories[name]()
+
+ return self._instances[name]
+
+ def clear_instance(self, name: str) -> bool:
+ """
+ 清除单例实例
+ Clear singleton instance
+
+ Args:
+ name: 单例名称
+
+ Returns:
+ bool: 是否成功清除
+ """
+ with self._lock:
+ if name in self._instances:
+ del self._instances[name]
+ return True
+ return False
+
+ def clear_all(self) -> None:
+ """清除所有单例实例"""
+ with self._lock:
+ self._instances.clear()
+
+ def get_registered_names(self) -> list:
+ """获取已注册的单例名称列表"""
+ with self._lock:
+ return list(self._factories.keys())
+
+ def get_active_instances(self) -> list:
+ """获取已创建的实例名称列表"""
+ with self._lock:
+ return list(self._instances.keys())
+
+
+# 全局单例管理器
+_singleton_manager = SingletonManager()
+
+
+def register_singleton(name: str):
+ """
+ 单例注册装饰器
+ Singleton registration decorator
+
+ Args:
+ name: 单例名称
+ """
+
+ def decorator(factory_func: Callable[[], T]) -> Callable[[], T]:
+ _singleton_manager.register_factory(name, factory_func)
+
+ @wraps(factory_func)
+ def wrapper() -> T:
+ return _singleton_manager.get_instance(name)
+
+ return wrapper
+
+ return decorator
+
+
+def get_singleton(name: str) -> Any:
+ """
+ 获取单例实例
+ Get singleton instance
+
+ Args:
+ name: 单例名称
+
+ Returns:
+ Any: 单例实例
+ """
+ return _singleton_manager.get_instance(name)
+
+
+def clear_singleton(name: str) -> bool:
+ """
+ 清除单例实例
+ Clear singleton instance
+
+ Args:
+ name: 单例名称
+
+ Returns:
+ bool: 是否成功清除
+ """
+ return _singleton_manager.clear_instance(name)
+
+
+def get_singleton_manager() -> SingletonManager:
+ """获取单例管理器实例"""
+ return _singleton_manager
+
+
+# 便捷的单例基类
+class SingletonMeta(type):
+ """单例元类"""
+
+ _instances = {}
+ _lock = threading.RLock()
+
+ def __call__(cls, *args, **kwargs):
+ if cls not in cls._instances:
+ with cls._lock:
+ if cls not in cls._instances:
+ cls._instances[cls] = super().__call__(*args, **kwargs)
+ return cls._instances[cls]
+
+
+class SingletonBase(metaclass=SingletonMeta):
+ """单例基类"""
+
+ pass
diff --git a/src/interactive_feedback_server/core/stats_collector.py b/src/interactive_feedback_server/core/stats_collector.py
new file mode 100644
index 0000000..4b41a5b
--- /dev/null
+++ b/src/interactive_feedback_server/core/stats_collector.py
@@ -0,0 +1,379 @@
+# interactive_feedback_server/core/stats_collector.py
+
+"""
+统一的统计收集器 - 优化版本
+Unified Statistics Collector - Optimized Version
+
+消除重复的统计收集逻辑,提供统一的统计管理。
+Eliminates duplicate statistics collection logic, provides unified statistics management.
+"""
+
+import time
+import threading
+from typing import Dict, Any, Optional, List, Union
+from collections import defaultdict, deque
+from dataclasses import dataclass, field
+
+
+@dataclass
+class StatEntry:
+ """统计条目"""
+
+ name: str
+ value: Union[int, float]
+ timestamp: float
+ tags: Dict[str, str] = field(default_factory=dict)
+ category: str = "general"
+
+
+class UnifiedStatsCollector:
+ """
+ 统一统计收集器
+ Unified Statistics Collector
+
+ 提供统一的统计收集、聚合和查询功能
+ Provides unified statistics collection, aggregation and query functionality
+ """
+
+ def __init__(self, max_history: int = 1000):
+ """
+ 初始化统计收集器
+ Initialize statistics collector
+
+ Args:
+ max_history: 最大历史记录数
+ """
+ self.max_history = max_history
+
+ # 统计存储
+ self._counters: Dict[str, Union[int, float]] = defaultdict(lambda: 0)
+ self._gauges: Dict[str, Union[int, float]] = {}
+ self._histograms: Dict[str, List[float]] = defaultdict(list)
+ self._history: deque = deque(maxlen=max_history)
+
+ # 分类统计
+ self._category_stats: Dict[str, Dict[str, Any]] = defaultdict(
+ lambda: {
+ "count": 0,
+ "total": 0,
+ "min": float("inf"),
+ "max": float("-inf"),
+ "avg": 0,
+ }
+ )
+
+ # 线程安全
+ self._lock = threading.RLock()
+
+ # 元数据
+ self._start_time = time.time()
+ self._last_reset = time.time()
+
+ def increment(
+ self, name: str, value: Union[int, float] = 1, category: str = "general", **tags
+ ) -> None:
+ """
+ 增加计数器
+ Increment counter
+
+ Args:
+ name: 统计名称
+ value: 增加值
+ category: 分类
+ **tags: 标签
+ """
+ with self._lock:
+ self._counters[name] += value
+
+ # 记录历史
+ entry = StatEntry(
+ name=name,
+ value=self._counters[name],
+ timestamp=time.time(),
+ tags=tags,
+ category=category,
+ )
+ self._history.append(entry)
+
+ # 更新分类统计
+ self._update_category_stats(category, value)
+
+ def set_gauge(
+ self, name: str, value: Union[int, float], category: str = "general", **tags
+ ) -> None:
+ """
+ 设置仪表值
+ Set gauge value
+
+ Args:
+ name: 统计名称
+ value: 值
+ category: 分类
+ **tags: 标签
+ """
+ with self._lock:
+ self._gauges[name] = value
+
+ # 记录历史
+ entry = StatEntry(
+ name=name,
+ value=value,
+ timestamp=time.time(),
+ tags=tags,
+ category=category,
+ )
+ self._history.append(entry)
+
+ def record_value(
+ self, name: str, value: float, category: str = "general", **tags
+ ) -> None:
+ """
+ 记录数值到直方图
+ Record value to histogram
+
+ Args:
+ name: 统计名称
+ value: 值
+ category: 分类
+ **tags: 标签
+ """
+ with self._lock:
+ self._histograms[name].append(value)
+
+ # 保持历史记录限制
+ if len(self._histograms[name]) > self.max_history:
+ self._histograms[name] = self._histograms[name][-self.max_history :]
+
+ # 记录历史
+ entry = StatEntry(
+ name=name,
+ value=value,
+ timestamp=time.time(),
+ tags=tags,
+ category=category,
+ )
+ self._history.append(entry)
+
+ # 更新分类统计
+ self._update_category_stats(category, value)
+
+ def _update_category_stats(self, category: str, value: Union[int, float]) -> None:
+ """更新分类统计"""
+ stats = self._category_stats[category]
+ stats["count"] += 1
+ stats["total"] += value
+ stats["min"] = min(stats["min"], value)
+ stats["max"] = max(stats["max"], value)
+ stats["avg"] = stats["total"] / stats["count"]
+
+ def get_counter(self, name: str) -> Union[int, float]:
+ """获取计数器值"""
+ with self._lock:
+ return self._counters.get(name, 0)
+
+ def get_gauge(self, name: str) -> Optional[Union[int, float]]:
+ """获取仪表值"""
+ with self._lock:
+ return self._gauges.get(name)
+
+ def get_histogram_stats(self, name: str) -> Dict[str, float]:
+ """获取直方图统计"""
+ with self._lock:
+ values = self._histograms.get(name, [])
+ if not values:
+ return {}
+
+ sorted_values = sorted(values)
+ count = len(sorted_values)
+
+ return {
+ "count": count,
+ "min": min(sorted_values),
+ "max": max(sorted_values),
+ "mean": sum(sorted_values) / count,
+ "median": sorted_values[count // 2],
+ "p95": sorted_values[int(count * 0.95)] if count > 0 else 0,
+ "p99": sorted_values[int(count * 0.99)] if count > 0 else 0,
+ }
+
+ def get_category_stats(self, category: str = None) -> Dict[str, Any]:
+ """
+ 获取分类统计
+ Get category statistics
+
+ Args:
+ category: 分类名称,None表示所有分类
+
+ Returns:
+ Dict[str, Any]: 分类统计
+ """
+ with self._lock:
+ if category:
+ return dict(self._category_stats.get(category, {}))
+ else:
+ return {cat: dict(stats) for cat, stats in self._category_stats.items()}
+
+ def get_all_stats(self) -> Dict[str, Any]:
+ """
+ 获取所有统计信息
+ Get all statistics
+
+ Returns:
+ Dict[str, Any]: 所有统计信息
+ """
+ with self._lock:
+ current_time = time.time()
+
+ return {
+ "counters": dict(self._counters),
+ "gauges": dict(self._gauges),
+ "histograms": {
+ name: self.get_histogram_stats(name) for name in self._histograms
+ },
+ "categories": self.get_category_stats(),
+ "metadata": {
+ "start_time": self._start_time,
+ "last_reset": self._last_reset,
+ "uptime_seconds": current_time - self._start_time,
+ "total_entries": len(self._history),
+ "collection_time": current_time,
+ },
+ }
+
+ def get_recent_entries(
+ self, limit: int = 10, category: str = None
+ ) -> List[StatEntry]:
+ """
+ 获取最近的统计条目
+ Get recent statistics entries
+
+ Args:
+ limit: 限制数量
+ category: 分类过滤
+
+ Returns:
+ List[StatEntry]: 最近的条目
+ """
+ with self._lock:
+ entries = list(self._history)
+
+ if category:
+ entries = [e for e in entries if e.category == category]
+
+ return entries[-limit:]
+
+ def reset_stats(self, category: str = None) -> None:
+ """
+ 重置统计信息
+ Reset statistics
+
+ Args:
+ category: 分类名称,None表示重置所有
+ """
+ with self._lock:
+ if category:
+ # 重置特定分类
+ keys_to_remove = []
+ for entry in self._history:
+ if entry.category == category:
+ if entry.name in self._counters:
+ keys_to_remove.append(entry.name)
+
+ for key in keys_to_remove:
+ if key in self._counters:
+ del self._counters[key]
+ if key in self._gauges:
+ del self._gauges[key]
+ if key in self._histograms:
+ del self._histograms[key]
+
+ if category in self._category_stats:
+ del self._category_stats[category]
+ else:
+ # 重置所有
+ self._counters.clear()
+ self._gauges.clear()
+ self._histograms.clear()
+ self._category_stats.clear()
+ self._history.clear()
+
+ self._last_reset = time.time()
+
+ def export_stats(self, format: str = "json") -> str:
+ """
+ 导出统计信息
+ Export statistics
+
+ Args:
+ format: 导出格式 ('json', 'csv')
+
+ Returns:
+ str: 导出的数据
+ """
+ stats = self.get_all_stats()
+
+ if format.lower() == "json":
+ import json
+
+ return json.dumps(stats, indent=2, ensure_ascii=False)
+ elif format.lower() == "csv":
+ lines = ["timestamp,category,name,value,type"]
+
+ for entry in self._history:
+ entry_type = (
+ "counter"
+ if entry.name in self._counters
+ else "gauge" if entry.name in self._gauges else "histogram"
+ )
+ lines.append(
+ f"{entry.timestamp},{entry.category},{entry.name},{entry.value},{entry_type}"
+ )
+
+ return "\n".join(lines)
+ else:
+ raise ValueError(f"不支持的导出格式: {format}")
+
+
+# 全局统计收集器实例
+_global_stats_collector: Optional[UnifiedStatsCollector] = None
+
+
+def get_stats_collector() -> UnifiedStatsCollector:
+ """
+ 获取全局统计收集器实例
+ Get global statistics collector instance
+
+ Returns:
+ UnifiedStatsCollector: 统计收集器实例
+ """
+ global _global_stats_collector
+ if _global_stats_collector is None:
+ _global_stats_collector = UnifiedStatsCollector()
+ return _global_stats_collector
+
+
+# 便捷函数
+def increment_stat(
+ name: str, value: Union[int, float] = 1, category: str = "general", **tags
+) -> None:
+ """增加统计计数"""
+ get_stats_collector().increment(name, value, category, **tags)
+
+
+def set_stat_gauge(
+ name: str, value: Union[int, float], category: str = "general", **tags
+) -> None:
+ """设置统计仪表"""
+ get_stats_collector().set_gauge(name, value, category, **tags)
+
+
+def record_stat_value(
+ name: str, value: float, category: str = "general", **tags
+) -> None:
+ """记录统计值"""
+ get_stats_collector().record_value(name, value, category, **tags)
+
+
+def get_all_stats() -> Dict[str, Any]:
+ """获取所有统计信息"""
+ return get_stats_collector().get_all_stats()
diff --git a/src/interactive_feedback_server/error_handling/__init__.py b/src/interactive_feedback_server/error_handling/__init__.py
new file mode 100644
index 0000000..409df54
--- /dev/null
+++ b/src/interactive_feedback_server/error_handling/__init__.py
@@ -0,0 +1,61 @@
+# interactive_feedback_server/error_handling/__init__.py
+
+"""
+错误处理模块
+Error Handling Module
+
+提供分级错误处理、自动恢复和系统稳定性保障功能。
+Provides hierarchical error handling, automatic recovery and system stability assurance functionality.
+"""
+
+from .error_types import (
+ ErrorLevel,
+ ErrorCategory,
+ RecoveryStrategy,
+ ErrorContext,
+ ErrorInfo,
+ SystemError,
+ ValidationError,
+ ConfigurationError,
+ PluginError,
+ PerformanceError,
+ ExternalServiceError,
+ create_error_context,
+ create_system_error,
+)
+
+from .error_handler import ErrorHandler, get_error_handler
+
+from .recovery_manager import (
+ RecoveryManager,
+ RecoveryTask,
+ RecoveryStatus,
+ get_recovery_manager,
+)
+
+__all__ = [
+ # 错误类型
+ "ErrorLevel",
+ "ErrorCategory",
+ "RecoveryStrategy",
+ "ErrorContext",
+ "ErrorInfo",
+ "SystemError",
+ "ValidationError",
+ "ConfigurationError",
+ "PluginError",
+ "PerformanceError",
+ "ExternalServiceError",
+ "create_error_context",
+ "create_system_error",
+ # 错误处理器
+ "ErrorHandler",
+ "get_error_handler",
+ # 恢复管理器
+ "RecoveryManager",
+ "RecoveryTask",
+ "RecoveryStatus",
+ "get_recovery_manager",
+]
+
+__version__ = "3.3.0"
diff --git a/src/interactive_feedback_server/error_handling/error_handler.py b/src/interactive_feedback_server/error_handling/error_handler.py
new file mode 100644
index 0000000..140b722
--- /dev/null
+++ b/src/interactive_feedback_server/error_handling/error_handler.py
@@ -0,0 +1,434 @@
+# interactive_feedback_server/error_handling/error_handler.py
+
+"""
+错误处理器 - V3.3 架构改进版本
+Error Handler - V3.3 Architecture Improvement Version
+
+提供分级错误处理和自动恢复机制。
+Provides hierarchical error handling and automatic recovery mechanisms.
+"""
+
+import time
+import threading
+from typing import Dict, List, Any, Optional, Callable, Union
+from collections import defaultdict, deque
+import logging
+
+from .error_types import (
+ ErrorLevel,
+ ErrorCategory,
+ RecoveryStrategy,
+ ErrorContext,
+ SystemError,
+ create_error_context,
+ create_system_error,
+)
+
+
+class ErrorHandler:
+ """
+ 错误处理器
+ Error Handler
+
+ 提供分级错误处理、恢复策略和错误统计功能
+ Provides hierarchical error handling, recovery strategies and error statistics
+ """
+
+ def __init__(self, max_error_history: int = 1000):
+ """
+ 初始化错误处理器
+ Initialize error handler
+
+ Args:
+ max_error_history: 最大错误历史记录数
+ """
+ self.max_error_history = max_error_history
+
+ # 错误存储
+ self._error_history: deque = deque(maxlen=max_error_history)
+ self._error_counts: Dict[str, int] = defaultdict(int)
+ self._recovery_handlers: Dict[RecoveryStrategy, Callable] = {}
+
+ # 线程安全
+ self._lock = threading.RLock()
+
+ # 错误统计
+ self._stats = {
+ "total_errors": 0,
+ "errors_by_level": defaultdict(int),
+ "errors_by_category": defaultdict(int),
+ "recovery_attempts": defaultdict(int),
+ "recovery_successes": defaultdict(int),
+ }
+
+ # 熔断器状态
+ self._circuit_breakers: Dict[str, Dict[str, Any]] = {}
+
+ # 初始化默认恢复处理器
+ self._setup_default_recovery_handlers()
+
+ # 日志记录器
+ self.logger = logging.getLogger(__name__)
+
+ def _setup_default_recovery_handlers(self) -> None:
+ """设置默认恢复处理器"""
+ self._recovery_handlers[RecoveryStrategy.RETRY] = self._handle_retry
+ self._recovery_handlers[RecoveryStrategy.FALLBACK] = self._handle_fallback
+ self._recovery_handlers[RecoveryStrategy.CIRCUIT_BREAKER] = (
+ self._handle_circuit_breaker
+ )
+ self._recovery_handlers[RecoveryStrategy.GRACEFUL_DEGRADATION] = (
+ self._handle_graceful_degradation
+ )
+ self._recovery_handlers[RecoveryStrategy.ESCALATE] = self._handle_escalate
+
+ def handle_error(
+ self, error: Union[SystemError, Exception], context: ErrorContext = None
+ ) -> Optional[Any]:
+ """
+ 处理错误
+ Handle error
+
+ Args:
+ error: 错误对象
+ context: 错误上下文
+
+ Returns:
+ Optional[Any]: 恢复结果
+ """
+ with self._lock:
+ # 转换为SystemError
+ if not isinstance(error, SystemError):
+ system_error = self._convert_to_system_error(error, context)
+ else:
+ system_error = error
+
+ # 记录错误
+ self._record_error(system_error)
+
+ # 执行恢复策略
+ recovery_result = self._execute_recovery(system_error)
+
+ # 记录日志
+ self._log_error(system_error, recovery_result)
+
+ return recovery_result
+
+ def _convert_to_system_error(
+ self, error: Exception, context: ErrorContext = None
+ ) -> SystemError:
+ """将普通异常转换为SystemError"""
+ if context is None:
+ context = create_error_context("unknown", "unknown")
+
+ # 根据异常类型确定错误分类
+ category = self._determine_error_category(error)
+ level = self._determine_error_level(error)
+
+ return create_system_error(
+ level=level,
+ category=category,
+ message=str(error),
+ description=f"未处理的异常: {type(error).__name__}",
+ context=context,
+ exception=error,
+ recovery_strategy=self._determine_recovery_strategy(error),
+ )
+
+ def _determine_error_category(self, error: Exception) -> ErrorCategory:
+ """确定错误分类"""
+ error_type = type(error).__name__
+
+ if "Network" in error_type or "Connection" in error_type:
+ return ErrorCategory.NETWORK
+ elif "Database" in error_type or "SQL" in error_type:
+ return ErrorCategory.DATABASE
+ elif "Validation" in error_type or "Value" in error_type:
+ return ErrorCategory.VALIDATION
+ elif "Permission" in error_type or "Auth" in error_type:
+ return ErrorCategory.AUTHORIZATION
+ elif "Config" in error_type:
+ return ErrorCategory.CONFIGURATION
+ else:
+ return ErrorCategory.SYSTEM
+
+ def _determine_error_level(self, error: Exception) -> ErrorLevel:
+ """确定错误级别"""
+ error_type = type(error).__name__
+
+ if error_type in ["SystemExit", "KeyboardInterrupt"]:
+ return ErrorLevel.FATAL
+ elif error_type in ["MemoryError", "OSError"]:
+ return ErrorLevel.CRITICAL
+ elif error_type in ["ValueError", "TypeError", "AttributeError"]:
+ return ErrorLevel.ERROR
+ else:
+ return ErrorLevel.WARNING
+
+ def _determine_recovery_strategy(self, error: Exception) -> RecoveryStrategy:
+ """确定恢复策略"""
+ error_type = type(error).__name__
+
+ if "Network" in error_type or "Connection" in error_type:
+ return RecoveryStrategy.RETRY
+ elif "Config" in error_type:
+ return RecoveryStrategy.FALLBACK
+ elif "Permission" in error_type:
+ return RecoveryStrategy.ESCALATE
+ else:
+ return RecoveryStrategy.GRACEFUL_DEGRADATION
+
+ def _record_error(self, error: SystemError) -> None:
+ """记录错误"""
+ self._error_history.append(error)
+ self._stats["total_errors"] += 1
+ self._stats["errors_by_level"][error.error_info.level.value] += 1
+ self._stats["errors_by_category"][error.error_info.category.value] += 1
+
+ # 更新错误计数
+ error_key = (
+ f"{error.error_info.category.value}_{error.error_info.context.component}"
+ )
+ self._error_counts[error_key] += 1
+
+ def _execute_recovery(self, error: SystemError) -> Optional[Any]:
+ """执行恢复策略"""
+ strategy = error.error_info.recovery_strategy
+
+ if strategy == RecoveryStrategy.NONE:
+ return None
+
+ self._stats["recovery_attempts"][strategy.value] += 1
+
+ try:
+ handler = self._recovery_handlers.get(strategy)
+ if handler:
+ result = handler(error)
+ if result is not None:
+ self._stats["recovery_successes"][strategy.value] += 1
+ return result
+ except Exception as e:
+ self.logger.error(f"恢复策略执行失败 {strategy.value}: {e}")
+
+ return None
+
+ def _handle_retry(self, error: SystemError) -> Optional[Any]:
+ """处理重试策略"""
+ error_info = error.error_info
+
+ if error_info.retry_count >= error_info.max_retries:
+ self.logger.warning(f"重试次数已达上限: {error_info.error_id}")
+ return None
+
+ error_info.retry_count += 1
+
+ # 指数退避
+ delay = min(2**error_info.retry_count, 60) # 最大60秒
+ time.sleep(delay)
+
+ self.logger.info(
+ f"重试 {error_info.retry_count}/{error_info.max_retries}: {error_info.error_id}"
+ )
+
+ return {"action": "retry", "delay": delay, "attempt": error_info.retry_count}
+
+ def _handle_fallback(self, error: SystemError) -> Optional[Any]:
+ """处理降级策略"""
+ error_info = error.error_info
+
+ # 根据错误类型提供不同的降级方案
+ if error_info.category == ErrorCategory.CONFIGURATION:
+ return {"action": "fallback", "data": "使用默认配置"}
+ elif error_info.category == ErrorCategory.EXTERNAL_SERVICE:
+ return {"action": "fallback", "data": "使用缓存数据"}
+ else:
+ return {"action": "fallback", "data": "使用简化功能"}
+
+ def _handle_circuit_breaker(self, error: SystemError) -> Optional[Any]:
+ """处理熔断器策略"""
+ error_info = error.error_info
+ component = error_info.context.component
+
+ # 获取或创建熔断器状态
+ if component not in self._circuit_breakers:
+ self._circuit_breakers[component] = {
+ "state": "closed", # closed, open, half_open
+ "failure_count": 0,
+ "last_failure_time": 0,
+ "failure_threshold": 5,
+ "timeout": 60, # 60秒后尝试半开
+ }
+
+ breaker = self._circuit_breakers[component]
+ current_time = time.time()
+
+ # 更新失败计数
+ breaker["failure_count"] += 1
+ breaker["last_failure_time"] = current_time
+
+ # 检查是否需要打开熔断器
+ if breaker["failure_count"] >= breaker["failure_threshold"]:
+ breaker["state"] = "open"
+ self.logger.warning(f"熔断器打开: {component}")
+ return {"action": "circuit_breaker", "state": "open"}
+
+ return None
+
+ def _handle_graceful_degradation(self, error: SystemError) -> Optional[Any]:
+ """处理优雅降级策略"""
+ error_info = error.error_info
+
+ # 根据组件类型提供不同的降级方案
+ if error_info.context.component == "plugin_manager":
+ return {"action": "graceful_degradation", "data": "禁用插件,使用核心功能"}
+ elif error_info.context.component == "performance_monitor":
+ return {"action": "graceful_degradation", "data": "禁用监控,保持基本功能"}
+ else:
+ return {"action": "graceful_degradation", "data": "降级到基本功能"}
+
+ def _handle_escalate(self, error: SystemError) -> Optional[Any]:
+ """处理升级策略"""
+ error_info = error.error_info
+
+ # 记录需要人工干预的错误
+ self.logger.critical(
+ f"错误需要升级处理: {error_info.error_id} - {error_info.message}"
+ )
+
+ return {"action": "escalate", "data": "已通知管理员"}
+
+ def _log_error(self, error: SystemError, recovery_result: Any) -> None:
+ """记录错误日志"""
+ error_info = error.error_info
+
+ log_data = {
+ "error_id": error_info.error_id,
+ "level": error_info.level.value,
+ "category": error_info.category.value,
+ "component": error_info.context.component,
+ "operation": error_info.context.operation,
+ "message": error_info.message,
+ "recovery_strategy": error_info.recovery_strategy.value,
+ "recovery_result": recovery_result,
+ }
+
+ if error_info.level == ErrorLevel.FATAL:
+ self.logger.critical(f"致命错误: {log_data}")
+ elif error_info.level == ErrorLevel.CRITICAL:
+ self.logger.critical(f"严重错误: {log_data}")
+ elif error_info.level == ErrorLevel.ERROR:
+ self.logger.error(f"错误: {log_data}")
+ elif error_info.level == ErrorLevel.WARNING:
+ self.logger.warning(f"警告: {log_data}")
+ else:
+ self.logger.info(f"信息: {log_data}")
+
+ def register_recovery_handler(
+ self, strategy: RecoveryStrategy, handler: Callable
+ ) -> None:
+ """
+ 注册自定义恢复处理器
+ Register custom recovery handler
+
+ Args:
+ strategy: 恢复策略
+ handler: 处理函数
+ """
+ with self._lock:
+ self._recovery_handlers[strategy] = handler
+
+ def get_error_statistics(self) -> Dict[str, Any]:
+ """
+ 获取错误统计信息
+ Get error statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ with self._lock:
+ return {
+ "total_errors": self._stats["total_errors"],
+ "errors_by_level": dict(self._stats["errors_by_level"]),
+ "errors_by_category": dict(self._stats["errors_by_category"]),
+ "recovery_attempts": dict(self._stats["recovery_attempts"]),
+ "recovery_successes": dict(self._stats["recovery_successes"]),
+ "error_counts": dict(self._error_counts),
+ "circuit_breakers": dict(self._circuit_breakers),
+ "error_history_size": len(self._error_history),
+ }
+
+ def get_recent_errors(self, limit: int = 10) -> List[SystemError]:
+ """
+ 获取最近的错误
+ Get recent errors
+
+ Args:
+ limit: 限制数量
+
+ Returns:
+ List[SystemError]: 最近的错误列表
+ """
+ with self._lock:
+ return list(self._error_history)[-limit:]
+
+ def clear_error_history(self) -> None:
+ """清空错误历史"""
+ with self._lock:
+ self._error_history.clear()
+ self._error_counts.clear()
+ self._stats = {
+ "total_errors": 0,
+ "errors_by_level": defaultdict(int),
+ "errors_by_category": defaultdict(int),
+ "recovery_attempts": defaultdict(int),
+ "recovery_successes": defaultdict(int),
+ }
+
+ def is_circuit_breaker_open(self, component: str) -> bool:
+ """
+ 检查熔断器是否打开
+ Check if circuit breaker is open
+
+ Args:
+ component: 组件名称
+
+ Returns:
+ bool: 是否打开
+ """
+ with self._lock:
+ if component not in self._circuit_breakers:
+ return False
+
+ breaker = self._circuit_breakers[component]
+ current_time = time.time()
+
+ # 检查是否可以尝试半开
+ if (
+ breaker["state"] == "open"
+ and current_time - breaker["last_failure_time"] > breaker["timeout"]
+ ):
+ breaker["state"] = "half_open"
+ self.logger.info(f"熔断器半开: {component}")
+
+ return breaker["state"] == "open"
+
+
+# 使用统一的单例管理器
+from ..core import register_singleton
+
+
+@register_singleton("error_handler")
+def create_error_handler() -> ErrorHandler:
+ """创建错误处理器实例"""
+ return ErrorHandler()
+
+
+def get_error_handler() -> ErrorHandler:
+ """
+ 获取全局错误处理器实例
+ Get global error handler instance
+
+ Returns:
+ ErrorHandler: 错误处理器实例
+ """
+ return create_error_handler()
diff --git a/src/interactive_feedback_server/error_handling/error_types.py b/src/interactive_feedback_server/error_handling/error_types.py
new file mode 100644
index 0000000..da423cb
--- /dev/null
+++ b/src/interactive_feedback_server/error_handling/error_types.py
@@ -0,0 +1,348 @@
+# interactive_feedback_server/error_handling/error_types.py
+
+"""
+错误类型定义 - V3.3 架构改进版本
+Error Types Definition - V3.3 Architecture Improvement Version
+
+定义系统中的各种错误类型和分级处理策略。
+Defines various error types and hierarchical handling strategies in the system.
+"""
+
+from enum import Enum
+from typing import Dict, Any, Optional, List
+from dataclasses import dataclass
+import time
+import traceback
+
+
+class ErrorLevel(Enum):
+ """错误级别枚举"""
+
+ DEBUG = "debug" # 调试信息
+ INFO = "info" # 一般信息
+ WARNING = "warning" # 警告
+ ERROR = "error" # 错误
+ CRITICAL = "critical" # 严重错误
+ FATAL = "fatal" # 致命错误
+
+
+class ErrorCategory(Enum):
+ """错误分类枚举"""
+
+ SYSTEM = "system" # 系统错误
+ NETWORK = "network" # 网络错误
+ DATABASE = "database" # 数据库错误
+ VALIDATION = "validation" # 验证错误
+ AUTHENTICATION = "authentication" # 认证错误
+ AUTHORIZATION = "authorization" # 授权错误
+ BUSINESS_LOGIC = "business_logic" # 业务逻辑错误
+ EXTERNAL_SERVICE = "external_service" # 外部服务错误
+ CONFIGURATION = "configuration" # 配置错误
+ PLUGIN = "plugin" # 插件错误
+ PERFORMANCE = "performance" # 性能错误
+
+
+class RecoveryStrategy(Enum):
+ """恢复策略枚举"""
+
+ NONE = "none" # 无恢复策略
+ RETRY = "retry" # 重试
+ FALLBACK = "fallback" # 降级处理
+ CIRCUIT_BREAKER = "circuit_breaker" # 熔断器
+ GRACEFUL_DEGRADATION = "graceful_degradation" # 优雅降级
+ RESTART = "restart" # 重启
+ ESCALATE = "escalate" # 升级处理
+
+
+@dataclass
+class ErrorContext:
+ """
+ 错误上下文
+ Error Context
+
+ 包含错误发生时的上下文信息
+ Contains context information when error occurs
+ """
+
+ timestamp: float # 错误发生时间
+ component: str # 发生错误的组件
+ operation: str # 执行的操作
+ additional_data: Optional[Dict[str, Any]] = None # 额外数据
+
+ def __post_init__(self):
+ if self.additional_data is None:
+ self.additional_data = {}
+
+
+@dataclass
+class ErrorInfo:
+ """
+ 错误信息
+ Error Information
+
+ 包含完整的错误信息和处理策略
+ Contains complete error information and handling strategy
+ """
+
+ error_id: str # 错误唯一标识
+ level: ErrorLevel # 错误级别
+ category: ErrorCategory # 错误分类
+ message: str # 错误消息
+ description: str # 详细描述
+ context: ErrorContext # 错误上下文
+ exception: Optional[Exception] = None # 原始异常
+ stack_trace: Optional[str] = None # 堆栈跟踪
+ recovery_strategy: RecoveryStrategy = RecoveryStrategy.NONE # 恢复策略
+ retry_count: int = 0 # 重试次数
+ max_retries: int = 3 # 最大重试次数
+ recovery_data: Optional[Dict[str, Any]] = None # 恢复数据
+
+ def __post_init__(self):
+ if self.recovery_data is None:
+ self.recovery_data = {}
+
+ # 自动提取堆栈跟踪
+ if self.exception and not self.stack_trace:
+ self.stack_trace = "".join(
+ traceback.format_exception(
+ type(self.exception), self.exception, self.exception.__traceback__
+ )
+ )
+
+
+class SystemError(Exception):
+ """
+ 系统错误基类
+ System Error Base Class
+
+ 所有系统错误的基类,包含错误信息
+ Base class for all system errors, contains error information
+ """
+
+ def __init__(self, error_info: ErrorInfo):
+ """
+ 初始化系统错误
+ Initialize system error
+
+ Args:
+ error_info: 错误信息
+ """
+ self.error_info = error_info
+ super().__init__(error_info.message)
+
+ def __str__(self) -> str:
+ return f"[{self.error_info.level.value.upper()}] {self.error_info.category.value}: {self.error_info.message}"
+
+ def __repr__(self) -> str:
+ return f"SystemError(id={self.error_info.error_id}, level={self.error_info.level.value}, category={self.error_info.category.value})"
+
+
+class ValidationError(SystemError):
+ """验证错误"""
+
+ def __init__(
+ self,
+ message: str,
+ field: str = None,
+ value: Any = None,
+ context: ErrorContext = None,
+ ):
+ error_info = ErrorInfo(
+ error_id=f"validation_{int(time.time() * 1000)}",
+ level=ErrorLevel.WARNING,
+ category=ErrorCategory.VALIDATION,
+ message=message,
+ description=f"验证失败: {message}",
+ context=context
+ or ErrorContext(
+ timestamp=time.time(),
+ component="validation",
+ operation="validate",
+ additional_data={"field": field, "value": value},
+ ),
+ recovery_strategy=RecoveryStrategy.NONE,
+ )
+ super().__init__(error_info)
+
+
+class ConfigurationError(SystemError):
+ """配置错误"""
+
+ def __init__(
+ self, message: str, config_key: str = None, context: ErrorContext = None
+ ):
+ error_info = ErrorInfo(
+ error_id=f"config_{int(time.time() * 1000)}",
+ level=ErrorLevel.ERROR,
+ category=ErrorCategory.CONFIGURATION,
+ message=message,
+ description=f"配置错误: {message}",
+ context=context
+ or ErrorContext(
+ timestamp=time.time(),
+ component="configuration",
+ operation="load_config",
+ additional_data={"config_key": config_key},
+ ),
+ recovery_strategy=RecoveryStrategy.FALLBACK,
+ )
+ super().__init__(error_info)
+
+
+class PluginError(SystemError):
+ """插件错误"""
+
+ def __init__(
+ self, message: str, plugin_name: str = None, context: ErrorContext = None
+ ):
+ error_info = ErrorInfo(
+ error_id=f"plugin_{int(time.time() * 1000)}",
+ level=ErrorLevel.ERROR,
+ category=ErrorCategory.PLUGIN,
+ message=message,
+ description=f"插件错误: {message}",
+ context=context
+ or ErrorContext(
+ timestamp=time.time(),
+ component="plugin_manager",
+ operation="plugin_operation",
+ additional_data={"plugin_name": plugin_name},
+ ),
+ recovery_strategy=RecoveryStrategy.GRACEFUL_DEGRADATION,
+ )
+ super().__init__(error_info)
+
+
+class PerformanceError(SystemError):
+ """性能错误"""
+
+ def __init__(
+ self,
+ message: str,
+ metric_name: str = None,
+ threshold: float = None,
+ actual_value: float = None,
+ context: ErrorContext = None,
+ ):
+ error_info = ErrorInfo(
+ error_id=f"performance_{int(time.time() * 1000)}",
+ level=ErrorLevel.WARNING,
+ category=ErrorCategory.PERFORMANCE,
+ message=message,
+ description=f"性能问题: {message}",
+ context=context
+ or ErrorContext(
+ timestamp=time.time(),
+ component="performance_monitor",
+ operation="performance_check",
+ additional_data={
+ "metric_name": metric_name,
+ "threshold": threshold,
+ "actual_value": actual_value,
+ },
+ ),
+ recovery_strategy=RecoveryStrategy.GRACEFUL_DEGRADATION,
+ )
+ super().__init__(error_info)
+
+
+class ExternalServiceError(SystemError):
+ """外部服务错误"""
+
+ def __init__(
+ self,
+ message: str,
+ service_name: str = None,
+ status_code: int = None,
+ context: ErrorContext = None,
+ ):
+ error_info = ErrorInfo(
+ error_id=f"external_{int(time.time() * 1000)}",
+ level=ErrorLevel.ERROR,
+ category=ErrorCategory.EXTERNAL_SERVICE,
+ message=message,
+ description=f"外部服务错误: {message}",
+ context=context
+ or ErrorContext(
+ timestamp=time.time(),
+ component="external_service",
+ operation="service_call",
+ additional_data={
+ "service_name": service_name,
+ "status_code": status_code,
+ },
+ ),
+ recovery_strategy=RecoveryStrategy.RETRY,
+ max_retries=3,
+ )
+ super().__init__(error_info)
+
+
+def create_error_context(
+ component: str,
+ operation: str,
+ **additional_data,
+) -> ErrorContext:
+ """
+ 创建错误上下文
+ Create error context
+
+ Args:
+ component: 组件名称
+ operation: 操作名称
+ **additional_data: 额外数据
+
+ Returns:
+ ErrorContext: 错误上下文
+ """
+ return ErrorContext(
+ timestamp=time.time(),
+ component=component,
+ operation=operation,
+ additional_data=additional_data,
+ )
+
+
+def create_system_error(
+ level: ErrorLevel,
+ category: ErrorCategory,
+ message: str,
+ description: str = None,
+ context: ErrorContext = None,
+ exception: Exception = None,
+ recovery_strategy: RecoveryStrategy = RecoveryStrategy.NONE,
+ max_retries: int = 3,
+) -> SystemError:
+ """
+ 创建系统错误
+ Create system error
+
+ Args:
+ level: 错误级别
+ category: 错误分类
+ message: 错误消息
+ description: 详细描述
+ context: 错误上下文
+ exception: 原始异常
+ recovery_strategy: 恢复策略
+ max_retries: 最大重试次数
+
+ Returns:
+ SystemError: 系统错误
+ """
+ error_info = ErrorInfo(
+ error_id=f"{category.value}_{int(time.time() * 1000)}",
+ level=level,
+ category=category,
+ message=message,
+ description=description or message,
+ context=context
+ or ErrorContext(
+ timestamp=time.time(), component="unknown", operation="unknown"
+ ),
+ exception=exception,
+ recovery_strategy=recovery_strategy,
+ max_retries=max_retries,
+ )
+
+ return SystemError(error_info)
diff --git a/src/interactive_feedback_server/error_handling/recovery_manager.py b/src/interactive_feedback_server/error_handling/recovery_manager.py
new file mode 100644
index 0000000..af076ce
--- /dev/null
+++ b/src/interactive_feedback_server/error_handling/recovery_manager.py
@@ -0,0 +1,487 @@
+# interactive_feedback_server/error_handling/recovery_manager.py
+
+"""
+恢复管理器 - V3.3 架构改进版本
+Recovery Manager - V3.3 Architecture Improvement Version
+
+提供自动恢复机制和系统稳定性保障。
+Provides automatic recovery mechanisms and system stability assurance.
+"""
+
+import time
+import threading
+from typing import Dict, List, Any, Optional, Callable, Tuple
+from dataclasses import dataclass
+from enum import Enum
+
+from .error_types import ErrorLevel, ErrorCategory, SystemError, ErrorContext
+from .error_handler import get_error_handler
+
+
+class RecoveryStatus(Enum):
+ """恢复状态枚举"""
+
+ PENDING = "pending" # 等待中
+ IN_PROGRESS = "in_progress" # 进行中
+ SUCCESS = "success" # 成功
+ FAILED = "failed" # 失败
+ CANCELLED = "cancelled" # 已取消
+
+
+@dataclass
+class RecoveryTask:
+ """
+ 恢复任务
+ Recovery Task
+ """
+
+ task_id: str # 任务ID
+ component: str # 组件名称
+ operation: str # 操作名称
+ recovery_function: Callable # 恢复函数
+ priority: int # 优先级 (1-10, 数字越小优先级越高)
+ max_attempts: int # 最大尝试次数
+ timeout: float # 超时时间(秒)
+ dependencies: List[str] # 依赖的任务ID
+ created_at: float # 创建时间
+ status: RecoveryStatus = RecoveryStatus.PENDING # 状态
+ attempts: int = 0 # 已尝试次数
+ last_attempt_at: Optional[float] = None # 最后尝试时间
+ error_message: Optional[str] = None # 错误消息
+ result: Optional[Any] = None # 恢复结果
+
+
+class RecoveryManager:
+ """
+ 恢复管理器
+ Recovery Manager
+
+ 管理系统的自动恢复任务和稳定性保障
+ Manages automatic recovery tasks and system stability assurance
+ """
+
+ def __init__(self):
+ """初始化恢复管理器"""
+ self.error_handler = get_error_handler()
+
+ # 恢复任务管理
+ self._recovery_tasks: Dict[str, RecoveryTask] = {}
+ self._task_queue: List[str] = [] # 按优先级排序的任务队列
+ self._running_tasks: Dict[str, threading.Thread] = {}
+
+ # 线程安全
+ self._lock = threading.RLock()
+
+ # 恢复统计
+ self._stats = {
+ "total_tasks": 0,
+ "successful_recoveries": 0,
+ "failed_recoveries": 0,
+ "cancelled_recoveries": 0,
+ "average_recovery_time": 0.0,
+ }
+
+ # 系统健康检查
+ self._health_checks: Dict[str, Callable] = {}
+ self._health_status: Dict[str, bool] = {}
+
+ # 自动恢复配置
+ self._auto_recovery_enabled = True
+ self._max_concurrent_recoveries = 3
+
+ # 启动恢复监控线程
+ self._monitor_thread = threading.Thread(
+ target=self._recovery_monitor, daemon=True
+ )
+ self._monitor_thread.start()
+
+ def register_recovery_function(
+ self,
+ component: str,
+ operation: str,
+ recovery_function: Callable,
+ priority: int = 5,
+ max_attempts: int = 3,
+ timeout: float = 30.0,
+ dependencies: List[str] = None,
+ ) -> str:
+ """
+ 注册恢复函数
+ Register recovery function
+
+ Args:
+ component: 组件名称
+ operation: 操作名称
+ recovery_function: 恢复函数
+ priority: 优先级
+ max_attempts: 最大尝试次数
+ timeout: 超时时间
+ dependencies: 依赖任务
+
+ Returns:
+ str: 任务ID
+ """
+ with self._lock:
+ task_id = f"{component}_{operation}_{int(time.time() * 1000)}"
+
+ task = RecoveryTask(
+ task_id=task_id,
+ component=component,
+ operation=operation,
+ recovery_function=recovery_function,
+ priority=priority,
+ max_attempts=max_attempts,
+ timeout=timeout,
+ dependencies=dependencies or [],
+ created_at=time.time(),
+ )
+
+ self._recovery_tasks[task_id] = task
+ self._add_to_queue(task_id)
+ self._stats["total_tasks"] += 1
+
+ return task_id
+
+ def _add_to_queue(self, task_id: str) -> None:
+ """将任务添加到队列"""
+ task = self._recovery_tasks[task_id]
+
+ # 按优先级插入队列
+ inserted = False
+ for i, existing_task_id in enumerate(self._task_queue):
+ existing_task = self._recovery_tasks[existing_task_id]
+ if task.priority < existing_task.priority:
+ self._task_queue.insert(i, task_id)
+ inserted = True
+ break
+
+ if not inserted:
+ self._task_queue.append(task_id)
+
+ def execute_recovery(self, task_id: str) -> bool:
+ """
+ 执行恢复任务
+ Execute recovery task
+
+ Args:
+ task_id: 任务ID
+
+ Returns:
+ bool: 是否成功启动
+ """
+ with self._lock:
+ if task_id not in self._recovery_tasks:
+ return False
+
+ task = self._recovery_tasks[task_id]
+
+ # 检查任务状态
+ if task.status != RecoveryStatus.PENDING:
+ return False
+
+ # 检查依赖
+ if not self._check_dependencies(task):
+ return False
+
+ # 检查并发限制
+ if len(self._running_tasks) >= self._max_concurrent_recoveries:
+ return False
+
+ # 启动恢复任务
+ task.status = RecoveryStatus.IN_PROGRESS
+ task.attempts += 1
+ task.last_attempt_at = time.time()
+
+ recovery_thread = threading.Thread(
+ target=self._execute_recovery_task, args=(task_id,), daemon=True
+ )
+
+ self._running_tasks[task_id] = recovery_thread
+ recovery_thread.start()
+
+ return True
+
+ def _check_dependencies(self, task: RecoveryTask) -> bool:
+ """检查任务依赖"""
+ for dep_id in task.dependencies:
+ if dep_id in self._recovery_tasks:
+ dep_task = self._recovery_tasks[dep_id]
+ if dep_task.status != RecoveryStatus.SUCCESS:
+ return False
+ return True
+
+ def _execute_recovery_task(self, task_id: str) -> None:
+ """执行恢复任务的具体逻辑"""
+ task = self._recovery_tasks[task_id]
+ start_time = time.time()
+
+ try:
+ # 设置超时
+ def timeout_handler():
+ time.sleep(task.timeout)
+ if task.status == RecoveryStatus.IN_PROGRESS:
+ task.status = RecoveryStatus.FAILED
+ task.error_message = "恢复任务超时"
+
+ timeout_thread = threading.Thread(target=timeout_handler, daemon=True)
+ timeout_thread.start()
+
+ # 执行恢复函数
+ result = task.recovery_function()
+
+ # 检查是否超时
+ if task.status == RecoveryStatus.IN_PROGRESS:
+ task.status = RecoveryStatus.SUCCESS
+ task.result = result
+ self._stats["successful_recoveries"] += 1
+
+ # 更新平均恢复时间
+ recovery_time = time.time() - start_time
+ self._update_average_recovery_time(recovery_time)
+
+ except Exception as e:
+ task.status = RecoveryStatus.FAILED
+ task.error_message = str(e)
+ self._stats["failed_recoveries"] += 1
+
+ # 记录错误
+ error_context = ErrorContext(
+ timestamp=time.time(),
+ component=task.component,
+ operation=f"recovery_{task.operation}",
+ additional_data={"task_id": task_id, "attempt": task.attempts},
+ )
+
+ self.error_handler.handle_error(e, error_context)
+
+ finally:
+ # 清理运行任务
+ with self._lock:
+ if task_id in self._running_tasks:
+ del self._running_tasks[task_id]
+
+ # 如果失败且还有重试机会,重新加入队列
+ if (
+ task.status == RecoveryStatus.FAILED
+ and task.attempts < task.max_attempts
+ ):
+ task.status = RecoveryStatus.PENDING
+ self._add_to_queue(task_id)
+
+ def _update_average_recovery_time(self, recovery_time: float) -> None:
+ """更新平均恢复时间"""
+ current_avg = self._stats["average_recovery_time"]
+ successful_count = self._stats["successful_recoveries"]
+
+ if successful_count == 1:
+ self._stats["average_recovery_time"] = recovery_time
+ else:
+ # 计算新的平均值
+ total_time = current_avg * (successful_count - 1) + recovery_time
+ self._stats["average_recovery_time"] = total_time / successful_count
+
+ def _recovery_monitor(self) -> None:
+ """恢复监控线程"""
+ while True:
+ try:
+ if self._auto_recovery_enabled:
+ self._process_recovery_queue()
+ self._perform_health_checks()
+
+ time.sleep(5) # 每5秒检查一次
+
+ except Exception as e:
+ print(f"恢复监控线程错误: {e}")
+ time.sleep(10)
+
+ def _process_recovery_queue(self) -> None:
+ """处理恢复队列"""
+ with self._lock:
+ # 处理队列中的任务
+ tasks_to_process = []
+ for task_id in self._task_queue[:]:
+ if len(self._running_tasks) >= self._max_concurrent_recoveries:
+ break
+
+ task = self._recovery_tasks[task_id]
+ if task.status == RecoveryStatus.PENDING and self._check_dependencies(
+ task
+ ):
+ tasks_to_process.append(task_id)
+ self._task_queue.remove(task_id)
+
+ # 启动任务
+ for task_id in tasks_to_process:
+ self.execute_recovery(task_id)
+
+ def _perform_health_checks(self) -> None:
+ """执行健康检查"""
+ for component, health_check in self._health_checks.items():
+ try:
+ is_healthy = health_check()
+ previous_status = self._health_status.get(component, True)
+ self._health_status[component] = is_healthy
+
+ # 如果组件从不健康变为健康,记录恢复
+ if not previous_status and is_healthy:
+ print(f"组件 {component} 已恢复健康")
+ elif previous_status and not is_healthy:
+ print(f"组件 {component} 健康检查失败")
+
+ # 触发自动恢复
+ self._trigger_auto_recovery(component)
+
+ except Exception as e:
+ self._health_status[component] = False
+ print(f"健康检查异常 {component}: {e}")
+
+ def _trigger_auto_recovery(self, component: str) -> None:
+ """触发自动恢复"""
+ # 查找该组件的恢复任务
+ for task_id, task in self._recovery_tasks.items():
+ if task.component == component and task.status == RecoveryStatus.PENDING:
+ self.execute_recovery(task_id)
+ break
+
+ def register_health_check(self, component: str, health_check: Callable) -> None:
+ """
+ 注册健康检查函数
+ Register health check function
+
+ Args:
+ component: 组件名称
+ health_check: 健康检查函数,返回bool
+ """
+ with self._lock:
+ self._health_checks[component] = health_check
+ self._health_status[component] = True
+
+ def cancel_recovery(self, task_id: str) -> bool:
+ """
+ 取消恢复任务
+ Cancel recovery task
+
+ Args:
+ task_id: 任务ID
+
+ Returns:
+ bool: 是否成功取消
+ """
+ with self._lock:
+ if task_id not in self._recovery_tasks:
+ return False
+
+ task = self._recovery_tasks[task_id]
+
+ if task.status == RecoveryStatus.PENDING:
+ task.status = RecoveryStatus.CANCELLED
+ if task_id in self._task_queue:
+ self._task_queue.remove(task_id)
+ self._stats["cancelled_recoveries"] += 1
+ return True
+
+ return False
+
+ def get_recovery_status(self, task_id: str) -> Optional[RecoveryTask]:
+ """
+ 获取恢复任务状态
+ Get recovery task status
+
+ Args:
+ task_id: 任务ID
+
+ Returns:
+ Optional[RecoveryTask]: 任务信息
+ """
+ with self._lock:
+ return self._recovery_tasks.get(task_id)
+
+ def get_recovery_statistics(self) -> Dict[str, Any]:
+ """
+ 获取恢复统计信息
+ Get recovery statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ with self._lock:
+ success_rate = 0.0
+ if self._stats["total_tasks"] > 0:
+ success_rate = (
+ self._stats["successful_recoveries"] / self._stats["total_tasks"]
+ ) * 100
+
+ return {
+ "total_tasks": self._stats["total_tasks"],
+ "successful_recoveries": self._stats["successful_recoveries"],
+ "failed_recoveries": self._stats["failed_recoveries"],
+ "cancelled_recoveries": self._stats["cancelled_recoveries"],
+ "success_rate_percent": round(success_rate, 2),
+ "average_recovery_time": round(self._stats["average_recovery_time"], 3),
+ "running_tasks": len(self._running_tasks),
+ "pending_tasks": len(self._task_queue),
+ "health_status": dict(self._health_status),
+ "auto_recovery_enabled": self._auto_recovery_enabled,
+ }
+
+ def set_auto_recovery(self, enabled: bool) -> None:
+ """
+ 设置自动恢复开关
+ Set auto recovery switch
+
+ Args:
+ enabled: 是否启用
+ """
+ with self._lock:
+ self._auto_recovery_enabled = enabled
+
+ def get_system_health(self) -> Dict[str, Any]:
+ """
+ 获取系统健康状态
+ Get system health status
+
+ Returns:
+ Dict[str, Any]: 健康状态
+ """
+ with self._lock:
+ total_components = len(self._health_status)
+ healthy_components = sum(
+ 1 for status in self._health_status.values() if status
+ )
+
+ overall_health = "healthy"
+ if total_components == 0:
+ overall_health = "unknown"
+ elif healthy_components == 0:
+ overall_health = "critical"
+ elif healthy_components < total_components:
+ overall_health = "degraded"
+
+ return {
+ "overall_health": overall_health,
+ "healthy_components": healthy_components,
+ "total_components": total_components,
+ "health_percentage": round(
+ (healthy_components / max(total_components, 1)) * 100, 1
+ ),
+ "component_status": dict(self._health_status),
+ "last_check_time": time.time(),
+ }
+
+
+# 全局恢复管理器实例
+_global_recovery_manager: Optional[RecoveryManager] = None
+
+
+def get_recovery_manager() -> RecoveryManager:
+ """
+ 获取全局恢复管理器实例
+ Get global recovery manager instance
+
+ Returns:
+ RecoveryManager: 恢复管理器实例
+ """
+ global _global_recovery_manager
+ if _global_recovery_manager is None:
+ _global_recovery_manager = RecoveryManager()
+ return _global_recovery_manager
diff --git a/src/interactive_feedback_server/llm/__init__.py b/src/interactive_feedback_server/llm/__init__.py
new file mode 100644
index 0000000..0426784
--- /dev/null
+++ b/src/interactive_feedback_server/llm/__init__.py
@@ -0,0 +1,43 @@
+"""
+LLM模块:提供多种大语言模型的统一接口
+
+此模块实现了适配器模式,支持多种LLM提供商:
+- OpenAI (GPT系列)
+- Google Gemini
+- DeepSeek
+- 其他兼容OpenAI API的模型
+
+主要组件:
+- base.py: 定义LLMProvider抽象基类
+- factory.py: 工厂函数,根据配置创建provider实例
+- openai_provider.py: OpenAI适配器实现
+- 其他provider实现...
+"""
+
+from .base import LLMProvider
+from .factory import get_llm_provider, validate_provider_config
+
+# Provider implementations
+try:
+ from .openai_provider import OpenAIProvider
+except ImportError:
+ OpenAIProvider = None
+
+try:
+ from .gemini_provider import GeminiProvider
+except ImportError:
+ GeminiProvider = None
+
+try:
+ from .volcengine_provider import VolcEngineProvider
+except ImportError:
+ VolcEngineProvider = None
+
+__all__ = [
+ "LLMProvider",
+ "get_llm_provider",
+ "validate_provider_config",
+ "OpenAIProvider",
+ "GeminiProvider",
+ "VolcEngineProvider",
+]
diff --git a/src/interactive_feedback_server/llm/base.py b/src/interactive_feedback_server/llm/base.py
new file mode 100644
index 0000000..d0f65b5
--- /dev/null
+++ b/src/interactive_feedback_server/llm/base.py
@@ -0,0 +1,38 @@
+"""
+LLM Provider抽象基类
+
+定义所有LLM提供商必须实现的统一接口
+"""
+
+from abc import ABC, abstractmethod
+
+
+class LLMProvider(ABC):
+ """定义所有 LLM Provider 必须遵循的接口。"""
+
+ @abstractmethod
+ def generate(self, prompt: str, system_prompt: str) -> str:
+ """
+ 根据给定的 prompt 生成文本。
+
+ Args:
+ prompt: 用户的主要输入或问题
+ system_prompt: 定义模型角色的系统级指令
+
+ Returns:
+ str: 模型生成的文本
+
+ Raises:
+ Exception: 当API调用失败时抛出异常
+ """
+ pass
+
+ @abstractmethod
+ def validate_config(self) -> tuple[bool, str]:
+ """
+ 验证当前provider的配置是否有效。
+
+ Returns:
+ tuple[bool, str]: (是否有效, 状态信息)
+ """
+ pass
diff --git a/src/interactive_feedback_server/llm/config_manager.py b/src/interactive_feedback_server/llm/config_manager.py
new file mode 100644
index 0000000..0fcf828
--- /dev/null
+++ b/src/interactive_feedback_server/llm/config_manager.py
@@ -0,0 +1,229 @@
+"""
+配置管理器
+
+统一管理LLM相关的配置读写、缓存和默认值
+"""
+
+import json
+import os
+from typing import Dict, Any
+from .constants import DEFAULT_OPTIMIZER_CONFIG, DEFAULT_PROVIDER_CONFIGS
+from .config_validator import get_config_validator
+
+
+class ConfigManager:
+ """
+ LLM配置管理器 - 简化版本
+ LLM Configuration Manager - Simplified Version
+
+ 委托给主配置管理器,专注于LLM相关的配置处理
+ Delegates to main config manager, focuses on LLM-related configuration handling
+ """
+
+ def __init__(self, config_file: str = "config.json"):
+ """
+ 初始化配置管理器
+
+ Args:
+ config_file: 配置文件路径(保留兼容性,实际使用主配置管理器)
+ """
+ self.config_file = config_file # 保留兼容性
+ self.validator = get_config_validator()
+
+ # 使用主配置管理器
+ from ..utils.config_manager import get_config, save_config
+
+ self._get_config = get_config
+ self._save_config = save_config
+
+ def _load_config_from_file(self) -> Dict[str, Any]:
+ """
+ 从文件加载配置 - 委托给主配置管理器
+ Load configuration from file - delegate to main config manager
+
+ Returns:
+ dict: 配置字典
+ """
+ # 直接使用主配置管理器
+ return self._get_config()
+
+ def _save_config_to_file(self, config: Dict[str, Any]) -> bool:
+ """
+ 保存配置到文件 - 委托给主配置管理器
+ Save configuration to file - delegate to main config manager
+
+ Args:
+ config: 配置字典
+
+ Returns:
+ bool: 是否保存成功
+ """
+ # 直接使用主配置管理器
+ return self._save_config(config)
+
+ def get_config(self) -> Dict[str, Any]:
+ """
+ 获取完整配置(支持环境变量)
+
+ Returns:
+ dict: 完整配置
+ """
+ # 直接从主配置管理器获取配置(包含环境变量支持)
+ config = self._load_config_from_file()
+
+ # 确保有默认的优化器配置
+ if "expression_optimizer" not in config:
+ config["expression_optimizer"] = DEFAULT_OPTIMIZER_CONFIG.copy()
+
+ return config
+
+ def get_optimizer_config(self) -> Dict[str, Any]:
+ """
+ 获取优化器配置
+
+ Returns:
+ dict: 优化器配置
+ """
+ config = self.get_config()
+ return config.get("expression_optimizer", DEFAULT_OPTIMIZER_CONFIG.copy())
+
+ def get_provider_config(self, provider_name: str) -> Dict[str, Any]:
+ """
+ 获取指定Provider的配置
+
+ Args:
+ provider_name: Provider名称
+
+ Returns:
+ dict: Provider配置
+ """
+ optimizer_config = self.get_optimizer_config()
+ providers = optimizer_config.get("providers", {})
+
+ # 如果没有配置,返回默认配置
+ if provider_name not in providers:
+ return DEFAULT_PROVIDER_CONFIGS.get(provider_name, {}).copy()
+
+ return providers[provider_name].copy()
+
+ def set_optimizer_config(self, optimizer_config: Dict[str, Any]) -> bool:
+ """
+ 设置优化器配置
+
+ Args:
+ optimizer_config: 优化器配置
+
+ Returns:
+ bool: 是否设置成功
+ """
+ # 验证配置
+ is_valid, message = self.validator.validate_optimizer_config(optimizer_config)
+ if not is_valid:
+ return False
+
+ # 获取完整配置
+ config = self.get_config()
+ config["expression_optimizer"] = optimizer_config
+
+ # 保存到文件
+ return self._save_config_to_file(config)
+
+ def set_provider_config(
+ self, provider_name: str, provider_config: Dict[str, Any]
+ ) -> bool:
+ """
+ 设置指定Provider的配置
+
+ Args:
+ provider_name: Provider名称
+ provider_config: Provider配置
+
+ Returns:
+ bool: 是否设置成功
+ """
+ # 验证配置
+ is_valid, message = self.validator.validate_provider_config(
+ provider_name, provider_config
+ )
+ if not is_valid:
+ return False
+
+ # 获取优化器配置
+ optimizer_config = self.get_optimizer_config()
+
+ # 确保providers字段存在
+ if "providers" not in optimizer_config:
+ optimizer_config["providers"] = {}
+
+ # 更新Provider配置
+ optimizer_config["providers"][provider_name] = provider_config
+
+ # 保存优化器配置
+ return self.set_optimizer_config(optimizer_config)
+
+ def set_active_provider(self, provider_name: str) -> bool:
+ """
+ 设置活动的Provider
+
+ Args:
+ provider_name: Provider名称
+
+ Returns:
+ bool: 是否设置成功
+ """
+ # 检查Provider是否支持
+ if provider_name not in self.validator.get_supported_providers():
+ return False
+
+ # 获取优化器配置
+ optimizer_config = self.get_optimizer_config()
+
+ # 检查Provider是否已配置
+ providers = optimizer_config.get("providers", {})
+ if provider_name not in providers:
+ # 使用默认配置
+ default_config = DEFAULT_PROVIDER_CONFIGS.get(provider_name, {})
+ if not default_config:
+ return False
+ providers[provider_name] = default_config.copy()
+ optimizer_config["providers"] = providers
+
+ # 设置活动Provider
+ optimizer_config["active_provider"] = provider_name
+
+ # 保存配置
+ return self.set_optimizer_config(optimizer_config)
+
+ def enable_optimizer(self, enabled: bool = True) -> bool:
+ """
+ 启用或禁用优化器
+
+ Args:
+ enabled: 是否启用
+
+ Returns:
+ bool: 是否设置成功
+ """
+ optimizer_config = self.get_optimizer_config()
+ optimizer_config["enabled"] = enabled
+ return self.set_optimizer_config(optimizer_config)
+
+
+# 全局配置管理器实例
+_global_config_manager = None
+
+
+def get_config_manager(config_file: str = "config.json") -> ConfigManager:
+ """
+ 获取全局配置管理器实例
+
+ Args:
+ config_file: 配置文件路径
+
+ Returns:
+ ConfigManager: 配置管理器实例
+ """
+ global _global_config_manager
+ if _global_config_manager is None:
+ _global_config_manager = ConfigManager(config_file)
+ return _global_config_manager
diff --git a/src/interactive_feedback_server/llm/config_validator.py b/src/interactive_feedback_server/llm/config_validator.py
new file mode 100644
index 0000000..353d9bc
--- /dev/null
+++ b/src/interactive_feedback_server/llm/config_validator.py
@@ -0,0 +1,233 @@
+"""
+配置验证器
+
+专门负责LLM配置的验证逻辑,分离关注点
+"""
+
+from typing import Dict, Tuple, List, Any
+from .constants import API_KEY_VALIDATION, SUPPORTED_MODELS, API_ENDPOINTS
+
+
+class ConfigValidator:
+ """
+ 配置验证器
+
+ 负责验证各种LLM Provider的配置是否正确
+ """
+
+ def __init__(self):
+ """初始化配置验证器"""
+ self.validation_rules = API_KEY_VALIDATION
+ self.supported_models = SUPPORTED_MODELS
+ self.api_endpoints = API_ENDPOINTS
+
+ def validate_api_key(self, provider_name: str, api_key: str) -> Tuple[bool, str]:
+ """
+ 验证API密钥格式
+
+ Args:
+ provider_name: Provider名称
+ api_key: API密钥
+
+ Returns:
+ tuple[bool, str]: (是否有效, 错误信息)
+ """
+ if not api_key:
+ return False, f"{provider_name} API密钥未配置"
+
+ # 检查占位符
+ placeholder_patterns = ["YOUR_", "_API_KEY_HERE", "your-actual-api-key"]
+ if any(pattern in api_key for pattern in placeholder_patterns):
+ return False, f"{provider_name} API密钥未配置(仍为占位符)"
+
+ # 获取验证规则
+ validation_rules = self.validation_rules.get(provider_name, {})
+
+ # 检查前缀
+ if "prefix" in validation_rules:
+ prefix = validation_rules["prefix"]
+ if not api_key.startswith(prefix):
+ return False, f"{provider_name} API密钥格式可能无效(应以{prefix}开头)"
+
+ # 检查长度
+ if "min_length" in validation_rules:
+ min_length = validation_rules["min_length"]
+ if len(api_key) < min_length:
+ return False, f"{provider_name} API密钥长度不足(至少{min_length}字符)"
+
+ # 检查UUID格式(火山引擎)
+ if validation_rules.get("format") == "uuid":
+ if "contains" in validation_rules:
+ required_char = validation_rules["contains"]
+ if required_char not in api_key:
+ return False, f"{provider_name} API密钥格式可能无效(应为UUID格式)"
+
+ return True, "API密钥格式有效"
+
+ def validate_model(self, provider_name: str, model: str) -> Tuple[bool, str]:
+ """
+ 验证模型是否支持
+
+ Args:
+ provider_name: Provider名称
+ model: 模型名称
+
+ Returns:
+ tuple[bool, str]: (是否支持, 错误信息)
+ """
+ if not model:
+ return True, "未指定模型,将使用默认模型"
+
+ # 对于用户配置的模型,我们采用宽松验证策略
+ # 只要模型名称不为空,就认为是有效的,让API自己验证
+ # 这样用户可以使用任何新发布的模型,而不需要等待代码更新
+ supported_models = self.supported_models.get(provider_name, [])
+ if model not in supported_models:
+ # 不再返回错误,而是给出警告信息
+ return True, f"模型 {model} 不在预定义列表中,但将尝试使用(如果API支持)"
+
+ return True, "模型支持"
+
+ def validate_base_url(self, base_url: str) -> Tuple[bool, str]:
+ """
+ 验证Base URL格式
+
+ Args:
+ base_url: API基础URL
+
+ Returns:
+ tuple[bool, str]: (是否有效, 错误信息)
+ """
+ if not base_url:
+ return True, "未指定Base URL,将使用默认值"
+
+ if not base_url.startswith(("http://", "https://")):
+ return False, "Base URL格式无效(应以http://或https://开头)"
+
+ return True, "Base URL格式有效"
+
+ def validate_provider_config(
+ self, provider_name: str, config: Dict[str, Any]
+ ) -> Tuple[bool, str]:
+ """
+ 验证完整的Provider配置
+
+ Args:
+ provider_name: Provider名称
+ config: 配置字典
+
+ Returns:
+ tuple[bool, str]: (是否有效, 错误信息)
+ """
+ # 验证API密钥
+ api_key = config.get("api_key", "")
+ is_valid, message = self.validate_api_key(provider_name, api_key)
+ if not is_valid:
+ return False, message
+
+ # 验证模型
+ model = config.get("model", "")
+ is_valid, message = self.validate_model(provider_name, model)
+ if not is_valid:
+ return False, message
+
+ # 验证Base URL(如果存在)
+ base_url = config.get("base_url")
+ if base_url:
+ is_valid, message = self.validate_base_url(base_url)
+ if not is_valid:
+ return False, message
+
+ return True, "配置有效"
+
+ def validate_optimizer_config(self, config: Dict[str, Any]) -> Tuple[bool, str]:
+ """
+ 验证完整的优化器配置
+
+ Args:
+ config: 优化器配置
+
+ Returns:
+ tuple[bool, str]: (是否有效, 错误信息)
+ """
+ # 检查基本结构
+ if not isinstance(config, dict):
+ return False, "配置格式无效(应为字典)"
+
+ # 检查是否启用
+ enabled = config.get("enabled", False)
+ if not enabled:
+ return True, "优化功能未启用"
+
+ # 检查活动Provider
+ active_provider = config.get("active_provider")
+ if not active_provider:
+ return False, "未指定活动的LLM Provider"
+
+ # 检查Provider配置
+ providers = config.get("providers", {})
+ if not providers:
+ return False, "未配置任何LLM Provider"
+
+ if active_provider not in providers:
+ return False, f"活动Provider '{active_provider}' 未在配置中找到"
+
+ # 验证活动Provider的配置
+ provider_config = providers[active_provider]
+ is_valid, message = self.validate_provider_config(
+ active_provider, provider_config
+ )
+ if not is_valid:
+ return False, f"{active_provider} 配置无效: {message}"
+
+ return True, f"优化器配置有效,使用 {active_provider}"
+
+ def get_supported_providers(self) -> List[str]:
+ """
+ 获取支持的Provider列表
+
+ Returns:
+ list[str]: 支持的Provider名称列表
+ """
+ return list(self.supported_models.keys())
+
+ def get_supported_models_for_provider(self, provider_name: str) -> List[str]:
+ """
+ 获取指定Provider支持的模型列表
+
+ Args:
+ provider_name: Provider名称
+
+ Returns:
+ list[str]: 支持的模型列表
+ """
+ return self.supported_models.get(provider_name, [])
+
+ def get_default_endpoint(self, provider_name: str) -> str:
+ """
+ 获取Provider的默认端点
+
+ Args:
+ provider_name: Provider名称
+
+ Returns:
+ str: 默认API端点
+ """
+ return self.api_endpoints.get(provider_name, "")
+
+
+# 全局配置验证器实例
+_global_validator = None
+
+
+def get_config_validator() -> ConfigValidator:
+ """
+ 获取全局配置验证器实例
+
+ Returns:
+ ConfigValidator: 验证器实例
+ """
+ global _global_validator
+ if _global_validator is None:
+ _global_validator = ConfigValidator()
+ return _global_validator
diff --git a/src/interactive_feedback_server/llm/constants.py b/src/interactive_feedback_server/llm/constants.py
new file mode 100644
index 0000000..a498a83
--- /dev/null
+++ b/src/interactive_feedback_server/llm/constants.py
@@ -0,0 +1,103 @@
+"""
+LLM模块常量定义
+
+统一管理所有LLM相关的常量,避免重复定义
+"""
+
+# 默认Provider配置
+DEFAULT_PROVIDER_CONFIGS = {
+ "openai": {
+ "api_key": "",
+ "model": "gpt-4o-mini",
+ "base_url": "https://api.openai.com/v1",
+ },
+ "gemini": {"api_key": "", "model": "gemini-2.0-flash"},
+ "deepseek": {
+ "api_key": "",
+ "base_url": "https://api.deepseek.com/v1",
+ "model": "deepseek-chat",
+ },
+ "volcengine": {
+ "api_key": "",
+ "base_url": "https://ark.cn-beijing.volces.com/api/v3",
+ "model": "deepseek-v3-250324",
+ },
+}
+
+# 默认优化器配置 - V4.2 用户友好版本
+DEFAULT_OPTIMIZER_CONFIG = {
+ "enabled": True, # V4.2 改为默认启用
+ "active_provider": "openai",
+ "providers": DEFAULT_PROVIDER_CONFIGS,
+}
+
+# 支持的模型列表
+SUPPORTED_MODELS = {
+ "openai": [
+ # OpenAI模型
+ "gpt-3.5-turbo",
+ "gpt-4",
+ "gpt-4-turbo",
+ "gpt-4o",
+ "gpt-4o-mini",
+ # DeepSeek模型(兼容OpenAI接口)
+ "deepseek-chat",
+ "deepseek-coder",
+ ],
+ "gemini": [
+ "gemini-2.0-flash",
+ "gemini-2.0-flash-lite",
+ "gemini-2.5-flash-preview-04-17",
+ "gemini-1.5-pro",
+ "gemini-1.5-flash",
+ "gemini-1.0-pro",
+ "gemini-pro",
+ ],
+ "deepseek": ["deepseek-chat", "deepseek-coder"],
+ "volcengine": [
+ "deepseek-v3-250324",
+ "doubao-pro-4k",
+ "doubao-pro-32k",
+ "doubao-pro-128k",
+ "doubao-lite-4k",
+ "doubao-lite-32k",
+ ],
+}
+
+# API端点配置
+API_ENDPOINTS = {
+ "openai": "https://api.openai.com/v1",
+ "gemini": "https://generativelanguage.googleapis.com/v1beta/openai/",
+ "deepseek": "https://api.deepseek.com/v1",
+ "volcengine": "https://ark.cn-beijing.volces.com/api/v3",
+}
+
+# 通用配置
+COMMON_CONFIG = {"timeout": 30, "temperature": 0.7, "max_tokens": 1024}
+
+# 错误消息模板
+ERROR_MESSAGES = {
+ "auth": "[ERROR:AUTH] {provider} API密钥无效,请检查配置",
+ "rate": "[ERROR:RATE] {provider} API调用频率过高,请稍后再试",
+ "timeout": "[ERROR:TIMEOUT] {provider} 请求超时,请稍后重试",
+ "request": "[ERROR:REQUEST] {provider} 请求参数无效,请检查输入内容",
+ "safety": "[ERROR:SAFETY] 内容被{provider}安全过滤器阻止,请修改输入",
+ "model": "[ERROR:MODEL] {provider} 模型 {model} 不存在或不支持",
+ "unknown": "[ERROR:UNKNOWN] {provider} 服务异常: {error}",
+}
+
+# Provider显示名称
+PROVIDER_DISPLAY_NAMES = {
+ "openai": {"zh_CN": "OpenAI", "en_US": "OpenAI"},
+ "gemini": {"zh_CN": "Google Gemini", "en_US": "Google Gemini"},
+ "deepseek": {"zh_CN": "DeepSeek", "en_US": "DeepSeek"},
+ "volcengine": {"zh_CN": "火山引擎", "en_US": "Huoshan"},
+}
+
+# API密钥验证规则
+API_KEY_VALIDATION = {
+ "openai": {"prefix": "sk-", "min_length": 30}, # 调整为更合理的长度
+ "gemini": {"prefix": "AIza", "min_length": 30},
+ "deepseek": {"prefix": "sk-", "min_length": 30},
+ "volcengine": {"format": "uuid", "min_length": 30, "contains": "-"}, # UUID格式
+}
diff --git a/src/interactive_feedback_server/llm/factory.py b/src/interactive_feedback_server/llm/factory.py
new file mode 100644
index 0000000..62b196c
--- /dev/null
+++ b/src/interactive_feedback_server/llm/factory.py
@@ -0,0 +1,120 @@
+"""
+LLM Provider工厂函数
+
+根据配置动态创建和管理LLM provider实例
+"""
+
+from typing import Optional, Tuple
+from .base import LLMProvider
+from .config_validator import get_config_validator
+from .config_manager import get_config_manager
+
+
+def validate_provider_config(provider_name: str, config: dict) -> Tuple[bool, str]:
+ """
+ 验证特定provider的配置是否有效
+
+ Args:
+ provider_name: provider名称 (如 'openai', 'gemini')
+ config: provider的配置字典
+
+ Returns:
+ tuple[bool, str]: (是否有效, 状态信息)
+ """
+ validator = get_config_validator()
+ return validator.validate_provider_config(provider_name, config)
+
+
+def get_llm_provider(config: dict = None) -> Tuple[Optional[LLMProvider], str]:
+ """
+ 根据配置,实例化并返回对应的 LLM Provider
+
+ Args:
+ config: expression_optimizer配置字典(可选,如果不提供则从配置管理器获取)
+
+ Returns:
+ tuple[LLMProvider | None, str]: (provider实例, 状态信息)
+ """
+ # 如果没有提供配置,从配置管理器获取
+ if config is None:
+ config_manager = get_config_manager()
+ config = config_manager.get_optimizer_config()
+
+ # 使用验证器验证配置
+ validator = get_config_validator()
+ is_valid, message = validator.validate_optimizer_config(config)
+ if not is_valid:
+ return None, f"配置无效: {message}"
+
+ active_provider_name = config.get("active_provider")
+ provider_config = config["providers"][active_provider_name]
+
+ # 创建provider实例
+ if active_provider_name == "openai":
+ try:
+ from .openai_provider import OpenAIProvider
+
+ return (
+ OpenAIProvider(
+ api_key=provider_config.get("api_key"),
+ base_url=provider_config.get("base_url"),
+ model=provider_config.get("model", "gpt-3.5-turbo"),
+ ),
+ "配置有效",
+ )
+ except ImportError:
+ return None, "OpenAI provider未安装"
+
+ elif active_provider_name == "gemini":
+ try:
+ from .gemini_provider import GeminiProvider
+
+ return (
+ GeminiProvider(
+ api_key=provider_config.get("api_key"),
+ model=provider_config.get("model", "gemini-2.0-flash"),
+ base_url=provider_config.get("base_url"),
+ ),
+ "配置有效",
+ )
+ except ImportError:
+ return (
+ None,
+ "Gemini provider未安装,请安装: pip install google-generativeai",
+ )
+
+ elif active_provider_name == "deepseek":
+ try:
+ from .openai_provider import OpenAIProvider # DeepSeek兼容OpenAI API
+
+ return (
+ OpenAIProvider(
+ api_key=provider_config.get("api_key"),
+ base_url=provider_config.get(
+ "base_url", "https://api.deepseek.com/v1"
+ ),
+ model=provider_config.get("model", "deepseek-chat"),
+ ),
+ "配置有效",
+ )
+ except ImportError:
+ return None, "DeepSeek provider未安装"
+
+ elif active_provider_name == "volcengine":
+ try:
+ from .volcengine_provider import VolcEngineProvider
+
+ return (
+ VolcEngineProvider(
+ api_key=provider_config.get("api_key"),
+ model=provider_config.get("model", "doubao-pro-4k"),
+ base_url=provider_config.get(
+ "base_url", "https://ark.cn-beijing.volces.com/api/v3"
+ ),
+ ),
+ "配置有效",
+ )
+ except ImportError:
+ return None, "火山引擎 provider未安装"
+
+ return None, f"不支持的provider: {active_provider_name}"
diff --git a/src/interactive_feedback_server/llm/gemini_provider.py b/src/interactive_feedback_server/llm/gemini_provider.py
new file mode 100644
index 0000000..6ad51c6
--- /dev/null
+++ b/src/interactive_feedback_server/llm/gemini_provider.py
@@ -0,0 +1,135 @@
+"""
+Google Gemini Provider实现
+
+使用OpenAI兼容的API端点,支持最新的gemini-2.0-flash模型
+"""
+
+import time
+import random
+from typing import Optional
+from .base import LLMProvider
+from .utils import (
+ create_openai_client,
+ handle_api_error,
+ create_chat_completion,
+ validate_api_key,
+ validate_model,
+)
+
+
+class GeminiProvider(LLMProvider):
+ """Google Gemini API适配器(使用OpenAI兼容接口)"""
+
+ def __init__(
+ self, api_key: str, model: str = "gemini-2.0-flash", base_url: str = None
+ ):
+ """
+ 初始化Gemini Provider
+
+ Args:
+ api_key: Google Gemini API密钥
+ model: 使用的模型名称
+ base_url: API基础URL,如果不提供则使用默认值
+ """
+ self.api_key = api_key
+ self.model = model
+ # 使用配置文件中的base_url,如果没有则使用默认值
+ self.base_url = (
+ base_url or "https://generativelanguage.googleapis.com/v1beta/openai/"
+ )
+ self._client = None
+ self.last_request_time = 0
+ self.min_interval = 2.0 # 减少间隔,因为使用OpenAI兼容接口
+
+ @property
+ def client(self):
+ """延迟初始化OpenAI兼容的Gemini客户端"""
+ if self._client is None:
+ self._client = create_openai_client(self.api_key, self.base_url)
+ return self._client
+
+ def generate(self, prompt: str, system_prompt: str) -> str:
+ """
+ 使用Gemini API生成文本(OpenAI兼容接口)
+
+ Args:
+ prompt: 用户输入
+ system_prompt: 系统提示词
+
+ Returns:
+ str: 生成的文本或错误信息
+ """
+ # 实现请求间隔控制
+ current_time = time.time()
+ time_since_last = current_time - self.last_request_time
+
+ if time_since_last < self.min_interval:
+ sleep_time = self.min_interval - time_since_last
+ time.sleep(sleep_time)
+
+ # 记录请求时间
+ self.last_request_time = time.time()
+
+ # 简单重试机制(仅针对频率限制)
+ max_retries = 3
+ for attempt in range(max_retries):
+ try:
+ messages = [
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": prompt},
+ ]
+ return create_chat_completion(self.client, self.model, messages)
+
+ except Exception as e:
+ error_str = str(e).lower()
+
+ # 如果是频率限制,进行重试
+ if (
+ "quota" in error_str or "rate" in error_str or "429" in error_str
+ ) and attempt < max_retries - 1:
+ wait_time = (2**attempt) + random.uniform(0, 1) # 指数退避
+ time.sleep(wait_time)
+ continue
+
+ # 使用统一的错误处理
+ return handle_api_error(e, "Gemini", self.model)
+
+ return "[ERROR:RATE] Gemini API重试次数已用完,请稍后再试"
+
+ def validate_config(self) -> tuple[bool, str]:
+ """
+ 验证Gemini配置
+
+ Returns:
+ tuple[bool, str]: (是否有效, 状态信息)
+ """
+ # 验证API密钥
+ is_valid, message = validate_api_key("gemini", self.api_key)
+ if not is_valid:
+ return False, message
+
+ # 验证模型
+ is_valid, message = validate_model("gemini", self.model)
+ if not is_valid:
+ return False, message
+
+ return True, "配置有效"
+
+ def test_connection(self) -> tuple[bool, str]:
+ """
+ 测试Gemini API连接
+
+ Returns:
+ tuple[bool, str]: (是否成功, 状态信息)
+ """
+ try:
+ # 发送一个简单的测试请求
+ test_response = self.generate("你好", "你是一个AI助手,请简短回复。")
+
+ if test_response.startswith("[ERROR:"):
+ return False, f"连接测试失败: {test_response}"
+ else:
+ return True, "Gemini API连接成功"
+
+ except Exception as e:
+ return False, f"连接测试异常: {str(e)}"
diff --git a/src/interactive_feedback_server/llm/openai_provider.py b/src/interactive_feedback_server/llm/openai_provider.py
new file mode 100644
index 0000000..5c27dda
--- /dev/null
+++ b/src/interactive_feedback_server/llm/openai_provider.py
@@ -0,0 +1,105 @@
+"""
+OpenAI Provider实现
+
+封装OpenAI API调用,提供统一的LLM接口
+"""
+
+from typing import Optional
+from .base import LLMProvider
+from .utils import (
+ create_openai_client,
+ handle_api_error,
+ create_chat_completion,
+ validate_api_key,
+ validate_model,
+)
+
+
+class OpenAIProvider(LLMProvider):
+ """OpenAI API适配器"""
+
+ def __init__(
+ self, api_key: str, base_url: Optional[str] = None, model: str = "gpt-3.5-turbo"
+ ):
+ """
+ 初始化OpenAI Provider
+
+ Args:
+ api_key: OpenAI API密钥
+ base_url: 可选的API基础URL(用于代理或兼容API)
+ model: 使用的模型名称
+ """
+ self.api_key = api_key
+ self.base_url = base_url
+ self.model = model
+ self._client = None
+
+ @property
+ def client(self):
+ """延迟初始化OpenAI客户端"""
+ if self._client is None:
+ self._client = create_openai_client(self.api_key, self.base_url)
+ return self._client
+
+ def generate(self, prompt: str, system_prompt: str) -> str:
+ """
+ 使用OpenAI API生成文本
+
+ Args:
+ prompt: 用户输入
+ system_prompt: 系统提示词
+
+ Returns:
+ str: 生成的文本或错误信息
+ """
+ try:
+ messages = [
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": prompt},
+ ]
+ return create_chat_completion(self.client, self.model, messages)
+
+ except Exception as e:
+ return handle_api_error(e, "OpenAI", self.model)
+
+ def validate_config(self) -> tuple[bool, str]:
+ """
+ 验证OpenAI配置
+
+ Returns:
+ tuple[bool, str]: (是否有效, 状态信息)
+ """
+ # 验证API密钥
+ is_valid, message = validate_api_key("openai", self.api_key)
+ if not is_valid:
+ return False, message
+
+ # 验证Base URL
+ if self.base_url and not self.base_url.startswith(("http://", "https://")):
+ return False, "Base URL格式无效"
+
+ # 验证模型
+ is_valid, message = validate_model("openai", self.model)
+ if not is_valid:
+ return False, message
+
+ return True, "配置有效"
+
+ def test_connection(self) -> tuple[bool, str]:
+ """
+ 测试OpenAI API连接
+
+ Returns:
+ tuple[bool, str]: (是否成功, 状态信息)
+ """
+ try:
+ # 发送一个简单的测试请求
+ test_response = self.generate("你好", "你是一个AI助手,请简短回复。")
+
+ if test_response.startswith("[ERROR:"):
+ return False, f"连接测试失败: {test_response}"
+ else:
+ return True, "OpenAI API连接成功"
+
+ except Exception as e:
+ return False, f"连接测试异常: {str(e)}"
diff --git a/src/interactive_feedback_server/llm/performance_manager.py b/src/interactive_feedback_server/llm/performance_manager.py
new file mode 100644
index 0000000..4ac2029
--- /dev/null
+++ b/src/interactive_feedback_server/llm/performance_manager.py
@@ -0,0 +1,253 @@
+"""
+简化的性能管理器
+
+提供基本的缓存和统计功能,移除过度复杂的异步和重试逻辑
+"""
+
+import hashlib
+from datetime import datetime, timedelta
+from typing import Dict, Tuple, Optional, Any
+from .base import LLMProvider
+
+
+class OptimizationManager:
+ """
+ 简化的优化管理器
+
+ 提供以下功能:
+ - 简单缓存:缓存相同输入的优化结果
+ - 基本统计:记录请求和缓存统计
+ """
+
+ def __init__(self, cache_ttl_minutes: int = 10):
+ """
+ 初始化优化管理器
+
+ Args:
+ cache_ttl_minutes: 缓存生存时间(分钟)
+ """
+ self.cache_ttl = timedelta(minutes=cache_ttl_minutes)
+
+ # 缓存存储:{cache_key: (result, timestamp)}
+ self.cache: Dict[str, Tuple[str, datetime]] = {}
+
+ # 统计信息
+ self.stats = {
+ "total_requests": 0,
+ "cache_hits": 0,
+ "cache_misses": 0,
+ "successful_requests": 0,
+ "failed_requests": 0,
+ }
+
+ def _get_cache_key(self, text: str, mode: str, reinforcement: str = "") -> str:
+ """
+ 生成缓存键
+
+ Args:
+ text: 原始文本
+ mode: 优化模式
+ reinforcement: 强化指令
+
+ Returns:
+ str: 缓存键
+ """
+ content = f"{text}|{mode}|{reinforcement}"
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]
+
+ def _is_cache_valid(self, timestamp: datetime) -> bool:
+ """
+ 检查缓存是否有效
+
+ Args:
+ timestamp: 缓存时间戳
+
+ Returns:
+ bool: 是否有效
+ """
+ return datetime.now() - timestamp < self.cache_ttl
+
+ def _cleanup_expired_cache(self):
+ """清理过期的缓存项"""
+ current_time = datetime.now()
+ expired_keys = [
+ key
+ for key, (_, timestamp) in self.cache.items()
+ if current_time - timestamp >= self.cache_ttl
+ ]
+
+ for key in expired_keys:
+ del self.cache[key]
+
+ def _is_basic_valid_result(self, result: str) -> bool:
+ """
+ 基本结果有效性检查 - V4.1 精简版本
+ Basic result validity check - V4.1 Simplified Version
+
+ Args:
+ result: 优化结果
+
+ Returns:
+ bool: 是否有效
+ """
+ if not result or not isinstance(result, str):
+ return False
+
+ # 只检查明显的错误信息,移除复杂的污染检测
+ if result.startswith("[ERROR") or result.startswith("[系统错误]"):
+ return False
+
+ # 基本长度检查
+ if len(result.strip()) < 2:
+ return False
+
+ return True
+
+ def get_cache_stats(self) -> Dict[str, Any]:
+ """
+ 获取缓存统计信息
+
+ Returns:
+ Dict: 统计信息
+ """
+ self._cleanup_expired_cache()
+
+ return {
+ "cache_size": len(self.cache),
+ "cache_hit_rate": (
+ self.stats["cache_hits"] / max(1, self.stats["total_requests"])
+ )
+ * 100,
+ **self.stats,
+ }
+
+ def optimize_with_cache(
+ self,
+ provider: LLMProvider,
+ text: str,
+ mode: str,
+ system_prompt: str,
+ reinforcement: str = "",
+ ) -> str:
+ """
+ 带缓存的优化处理(简化的同步版本)
+
+ Args:
+ provider: LLM提供商实例
+ text: 原始文本
+ mode: 优化模式
+ system_prompt: 系统提示词
+ reinforcement: 强化指令
+
+ Returns:
+ str: 优化结果
+ """
+ self.stats["total_requests"] += 1
+
+ # 检查缓存
+ cache_key = self._get_cache_key(text, mode, reinforcement)
+ if cache_key in self.cache:
+ result, timestamp = self.cache[cache_key]
+ if self._is_cache_valid(timestamp):
+ self.stats["cache_hits"] += 1
+ return f"[CACHED] {result}"
+
+ self.stats["cache_misses"] += 1
+
+ # 直接调用provider(同步)
+ try:
+ # 使用统一的提示词格式化函数
+ from ..cli import format_prompt_for_mode
+
+ prompt = format_prompt_for_mode(text, mode, reinforcement)
+
+ # 简化的重试机制:只重试一次
+ max_retries = 1
+ for attempt in range(max_retries + 1):
+ result = provider.generate(prompt, system_prompt)
+
+ # 检查API错误
+ if result.startswith("[ERROR"):
+ self.stats["failed_requests"] += 1
+ return result
+
+ # 基本有效性检查
+ if self._is_basic_valid_result(result):
+ # 结果有效,缓存并返回
+ self.cache[cache_key] = (result, datetime.now())
+ self.stats["successful_requests"] += 1
+ return result
+
+ # 如果还有重试机会且结果明显异常,进行重试
+ if attempt < max_retries and len(result.strip()) < 5:
+ continue
+
+ # 即使结果可能有小问题,也直接返回,避免过度检查
+ self.cache[cache_key] = (result, datetime.now())
+ self.stats["successful_requests"] += 1
+ return result
+
+ except Exception as e:
+ self.stats["failed_requests"] += 1
+ return f"[ERROR:UNKNOWN] 优化异常: {str(e)}"
+
+ def clear_cache(self):
+ """清空缓存"""
+ self.cache.clear()
+
+ def reset_stats(self):
+ """重置统计信息"""
+ for key in self.stats:
+ self.stats[key] = 0
+
+ # V4.1 移除:复杂的健康检查和缓存污染检测功能已删除
+ # 这些功能对于简单的文本优化来说过度复杂,影响性能
+
+
+# 全局性能管理器实例
+_global_manager: Optional[OptimizationManager] = None
+
+
+def get_optimization_manager(config: Dict[str, Any]) -> OptimizationManager:
+ """
+ 获取全局优化管理器实例
+
+ Args:
+ config: 性能配置
+
+ Returns:
+ OptimizationManager: 管理器实例
+ """
+ global _global_manager
+
+ if _global_manager is None:
+ performance_config = config.get("performance", {})
+
+ _global_manager = OptimizationManager(
+ cache_ttl_minutes=performance_config.get("cache_ttl_minutes", 10)
+ )
+
+ return _global_manager
+
+
+def reset_global_manager():
+ """
+ 重置全局管理器实例
+ Reset global manager instance
+ """
+ global _global_manager
+ _global_manager = None
+
+
+def force_clear_all_caches():
+ """
+ 强制清空所有缓存(包括重置全局实例)
+ Force clear all caches (including resetting global instance)
+ """
+ global _global_manager
+ if _global_manager is not None:
+ _global_manager.clear_cache()
+ _global_manager.reset_stats()
+
+ # 重置全局实例
+ _global_manager = None
diff --git a/src/interactive_feedback_server/llm/utils.py b/src/interactive_feedback_server/llm/utils.py
new file mode 100644
index 0000000..dfed1b0
--- /dev/null
+++ b/src/interactive_feedback_server/llm/utils.py
@@ -0,0 +1,178 @@
+"""
+LLM模块工具函数
+
+提供通用的工具函数,减少代码重复
+"""
+
+from typing import Optional, Tuple
+from .constants import ERROR_MESSAGES, COMMON_CONFIG
+
+
+def create_openai_client(api_key: str, base_url: Optional[str] = None):
+ """
+ 创建OpenAI兼容的客户端
+
+ Args:
+ api_key: API密钥
+ base_url: 可选的API基础URL
+
+ Returns:
+ OpenAI客户端实例
+
+ Raises:
+ ImportError: 如果openai库未安装
+ """
+ try:
+ from openai import OpenAI
+
+ return OpenAI(api_key=api_key, base_url=base_url)
+ except ImportError:
+ raise ImportError("请安装openai库: pip install openai")
+
+
+def handle_api_error(error: Exception, provider_name: str, model: str = "") -> str:
+ """
+ 统一的API错误处理
+
+ Args:
+ error: 异常对象
+ provider_name: Provider名称
+ model: 模型名称(可选)
+
+ Returns:
+ str: 格式化的错误消息
+ """
+ error_str = str(error).lower()
+ error_type = type(error).__name__
+
+ # 认证错误
+ if any(keyword in error_str for keyword in ["authentication", "401", "api_key"]):
+ return ERROR_MESSAGES["auth"].format(provider=provider_name)
+
+ # 频率限制
+ elif any(keyword in error_str for keyword in ["rate", "429", "quota"]):
+ return ERROR_MESSAGES["rate"].format(provider=provider_name)
+
+ # 超时错误
+ elif "timeout" in error_str or "TimeoutError" in error_type:
+ return ERROR_MESSAGES["timeout"].format(provider=provider_name)
+
+ # 请求参数错误
+ elif any(keyword in error_str for keyword in ["invalid", "400"]):
+ return ERROR_MESSAGES["request"].format(provider=provider_name)
+
+ # 安全过滤器
+ elif any(keyword in error_str for keyword in ["blocked", "safety"]):
+ return ERROR_MESSAGES["safety"].format(provider=provider_name)
+
+ # 模型不存在
+ elif "not found" in error_str or ("model" in error_str and model):
+ return ERROR_MESSAGES["model"].format(provider=provider_name, model=model)
+
+ # 未知错误
+ else:
+ return ERROR_MESSAGES["unknown"].format(
+ provider=provider_name, error=str(error)
+ )
+
+
+def create_chat_completion(client, model: str, messages: list, **kwargs) -> str:
+ """
+ 统一的聊天完成请求
+
+ Args:
+ client: OpenAI兼容的客户端
+ model: 模型名称
+ messages: 消息列表
+ **kwargs: 额外参数
+
+ Returns:
+ str: 生成的文本
+
+ Raises:
+ Exception: API调用失败时抛出异常
+ """
+ # 合并默认配置和用户配置
+ config = {**COMMON_CONFIG, **kwargs}
+
+ response = client.chat.completions.create(
+ model=model,
+ messages=messages,
+ temperature=config.get("temperature", 0.7),
+ max_tokens=config.get("max_tokens", 1024),
+ timeout=config.get("timeout", 30),
+ )
+
+ if response.choices and response.choices[0].message.content:
+ return response.choices[0].message.content
+ else:
+ return ""
+
+
+# 验证函数已迁移到config_validator.py
+# 为了向后兼容,提供简单的包装函数
+
+
+def validate_api_key(provider_name: str, api_key: str) -> Tuple[bool, str]:
+ """
+ 验证API密钥格式(向后兼容包装函数)
+
+ Args:
+ provider_name: Provider名称
+ api_key: API密钥
+
+ Returns:
+ tuple[bool, str]: (是否有效, 错误信息)
+ """
+ from .config_validator import get_config_validator
+
+ validator = get_config_validator()
+ return validator.validate_api_key(provider_name, api_key)
+
+
+def validate_model(provider_name: str, model: str) -> Tuple[bool, str]:
+ """
+ 验证模型是否支持(向后兼容包装函数)
+
+ Args:
+ provider_name: Provider名称
+ model: 模型名称
+
+ Returns:
+ tuple[bool, str]: (是否支持, 错误信息)
+ """
+ from .config_validator import get_config_validator
+
+ validator = get_config_validator()
+ return validator.validate_model(provider_name, model)
+
+
+def get_default_config(provider_name: str) -> dict:
+ """
+ 获取Provider的默认配置
+
+ Args:
+ provider_name: Provider名称
+
+ Returns:
+ dict: 默认配置
+ """
+ from .constants import DEFAULT_PROVIDER_CONFIGS
+
+ return DEFAULT_PROVIDER_CONFIGS.get(provider_name, {}).copy()
+
+
+def get_provider_display_name(provider_name: str, language: str = "zh_CN") -> str:
+ """
+ 获取Provider的显示名称
+
+ Args:
+ provider_name: Provider名称
+ language: 语言代码
+
+ Returns:
+ str: 显示名称
+ """
+ from .constants import PROVIDER_DISPLAY_NAMES
+
+ return PROVIDER_DISPLAY_NAMES.get(provider_name, {}).get(language, provider_name)
diff --git a/src/interactive_feedback_server/llm/volcengine_provider.py b/src/interactive_feedback_server/llm/volcengine_provider.py
new file mode 100644
index 0000000..3681863
--- /dev/null
+++ b/src/interactive_feedback_server/llm/volcengine_provider.py
@@ -0,0 +1,108 @@
+"""
+火山引擎(豆包)Provider实现
+
+封装火山引擎API调用,提供统一的LLM接口
+"""
+
+from typing import Optional
+from .base import LLMProvider
+from .utils import (
+ create_openai_client,
+ handle_api_error,
+ create_chat_completion,
+ validate_api_key,
+ validate_model,
+)
+
+
+class VolcEngineProvider(LLMProvider):
+ """火山引擎(豆包)API适配器"""
+
+ def __init__(
+ self,
+ api_key: str,
+ model: str = "deepseek-v3-250324",
+ base_url: str = "https://ark.cn-beijing.volces.com/api/v3",
+ ):
+ """
+ 初始化火山引擎 Provider
+
+ Args:
+ api_key: 火山引擎API密钥
+ model: 使用的模型名称
+ base_url: API基础URL
+ """
+ self.api_key = api_key
+ self.model = model
+ self.base_url = base_url
+ self._client = None
+
+ @property
+ def client(self):
+ """延迟初始化火山引擎客户端(使用OpenAI兼容接口)"""
+ if self._client is None:
+ self._client = create_openai_client(self.api_key, self.base_url)
+ return self._client
+
+ def generate(self, prompt: str, system_prompt: str) -> str:
+ """
+ 使用火山引擎API生成文本
+
+ Args:
+ prompt: 用户输入
+ system_prompt: 系统提示词
+
+ Returns:
+ str: 生成的文本或错误信息
+ """
+ try:
+ messages = [
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": prompt},
+ ]
+ return create_chat_completion(self.client, self.model, messages)
+
+ except Exception as e:
+ return handle_api_error(e, "火山引擎", self.model)
+
+ def validate_config(self) -> tuple[bool, str]:
+ """
+ 验证火山引擎配置
+
+ Returns:
+ tuple[bool, str]: (是否有效, 状态信息)
+ """
+ # 验证API密钥
+ is_valid, message = validate_api_key("volcengine", self.api_key)
+ if not is_valid:
+ return False, message
+
+ # 验证Base URL
+ if not self.base_url or not self.base_url.startswith(("http://", "https://")):
+ return False, "火山引擎Base URL格式无效"
+
+ # 验证模型
+ is_valid, message = validate_model("volcengine", self.model)
+ if not is_valid:
+ return False, message
+
+ return True, "配置有效"
+
+ def test_connection(self) -> tuple[bool, str]:
+ """
+ 测试火山引擎API连接
+
+ Returns:
+ tuple[bool, str]: (是否成功, 状态信息)
+ """
+ try:
+ # 发送一个简单的测试请求
+ test_response = self.generate("你好", "你是一个AI助手,请简短回复。")
+
+ if test_response.startswith("[ERROR:"):
+ return False, f"连接测试失败: {test_response}"
+ else:
+ return True, "火山引擎API连接成功"
+
+ except Exception as e:
+ return False, f"连接测试异常: {str(e)}"
diff --git a/src/interactive_feedback_server/monitoring/__init__.py b/src/interactive_feedback_server/monitoring/__init__.py
new file mode 100644
index 0000000..8166513
--- /dev/null
+++ b/src/interactive_feedback_server/monitoring/__init__.py
@@ -0,0 +1,34 @@
+# interactive_feedback_server/monitoring/__init__.py
+
+"""
+性能监控模块
+Performance Monitoring Module
+
+提供全面的性能监控、分析和可视化功能。
+Provides comprehensive performance monitoring, analysis and visualization functionality.
+"""
+
+from .performance_monitor import (
+ MetricCollector,
+ PerformanceTimer,
+ MetricType,
+ MetricData,
+ PerformanceSnapshot,
+ timer_decorator,
+ get_metric_collector,
+)
+
+# 已删除未使用的性能分析器和监控仪表板模块
+
+__all__ = [
+ # 性能监控
+ "MetricCollector",
+ "PerformanceTimer",
+ "MetricType",
+ "MetricData",
+ "PerformanceSnapshot",
+ "timer_decorator",
+ "get_metric_collector",
+]
+
+__version__ = "3.3.0"
diff --git a/src/interactive_feedback_server/monitoring/performance_monitor.py b/src/interactive_feedback_server/monitoring/performance_monitor.py
new file mode 100644
index 0000000..2a9c435
--- /dev/null
+++ b/src/interactive_feedback_server/monitoring/performance_monitor.py
@@ -0,0 +1,417 @@
+# interactive_feedback_server/monitoring/performance_monitor.py
+
+"""
+性能监控系统 - V3.3 架构改进版本
+Performance Monitoring System - V3.3 Architecture Improvement Version
+
+提供全面的性能监控、指标收集和分析功能。
+Provides comprehensive performance monitoring, metrics collection and analysis functionality.
+"""
+
+import time
+import threading
+import psutil
+import statistics
+from typing import Dict, List, Any, Callable
+from dataclasses import dataclass, field
+from collections import deque
+from enum import Enum
+
+
+class MetricType(Enum):
+ """指标类型枚举"""
+
+ COUNTER = "counter" # 计数器
+ GAUGE = "gauge" # 仪表
+ HISTOGRAM = "histogram" # 直方图
+ TIMER = "timer" # 计时器
+
+
+@dataclass
+class MetricData:
+ """
+ 指标数据
+ Metric Data
+ """
+
+ name: str # 指标名称
+ metric_type: MetricType # 指标类型
+ value: float # 当前值
+ timestamp: float # 时间戳
+ tags: Dict[str, str] = field(default_factory=dict) # 标签
+ unit: str = "" # 单位
+ description: str = "" # 描述
+
+
+@dataclass
+class PerformanceSnapshot:
+ """
+ 性能快照
+ Performance Snapshot
+ """
+
+ timestamp: float # 时间戳
+ cpu_percent: float # CPU使用率
+ memory_percent: float # 内存使用率
+ memory_used_mb: float # 已用内存(MB)
+ disk_io_read_mb: float # 磁盘读取(MB)
+ disk_io_write_mb: float # 磁盘写入(MB)
+ network_sent_mb: float # 网络发送(MB)
+ network_recv_mb: float # 网络接收(MB)
+ active_threads: int # 活跃线程数
+ open_files: int # 打开文件数
+
+
+class MetricCollector:
+ """
+ 指标收集器 - 简化版本
+ Metric Collector - Simplified Version
+
+ 专注于系统性能监控,通用统计功能委托给统计收集器
+ Focuses on system performance monitoring, delegates general statistics to stats collector
+ """
+
+ def __init__(self, max_history: int = 1000):
+ """
+ 初始化指标收集器
+ Initialize metric collector
+
+ Args:
+ max_history: 最大历史记录数
+ """
+ self.max_history = max_history
+ self._lock = threading.RLock()
+
+ # 系统指标收集
+ self._system_snapshots: deque = deque(maxlen=max_history)
+ self._last_disk_io = None
+ self._last_network_io = None
+
+ # 使用统一的统计收集器作为后端
+ from ..core import get_stats_collector
+
+ self._stats_collector = get_stats_collector()
+
+ def increment_counter(
+ self, name: str, value: float = 1.0, tags: Dict[str, str] = None
+ ) -> None:
+ """
+ 增加计数器 - 委托给统计收集器
+ Increment counter - delegate to stats collector
+
+ Args:
+ name: 指标名称
+ value: 增加值
+ tags: 标签
+ """
+ # 委托给统一的统计收集器
+ self._stats_collector.increment(
+ name, value, category="performance_monitor", **(tags or {})
+ )
+
+ def set_gauge(self, name: str, value: float, tags: Dict[str, str] = None) -> None:
+ """
+ 设置仪表值 - 委托给统计收集器
+ Set gauge value - delegate to stats collector
+
+ Args:
+ name: 指标名称
+ value: 值
+ tags: 标签
+ """
+ # 委托给统一的统计收集器
+ self._stats_collector.set_gauge(
+ name, value, category="performance_monitor", **(tags or {})
+ )
+
+ def record_timer(
+ self, name: str, duration: float, tags: Dict[str, str] = None
+ ) -> None:
+ """
+ 记录计时器 - 委托给统计收集器
+ Record timer - delegate to stats collector
+
+ Args:
+ name: 指标名称
+ duration: 持续时间(秒)
+ tags: 标签
+ """
+ # 委托给统一的统计收集器
+ self._stats_collector.record_value(
+ name, duration, category="performance_timer", **(tags or {})
+ )
+
+ # 已删除 _record_metric 方法,功能委托给统计收集器
+
+ def collect_system_metrics(self) -> PerformanceSnapshot:
+ """
+ 收集系统指标
+ Collect system metrics
+
+ Returns:
+ PerformanceSnapshot: 性能快照
+ """
+ try:
+ # CPU和内存
+ cpu_percent = psutil.cpu_percent(interval=0.1)
+ memory = psutil.virtual_memory()
+
+ # 磁盘IO
+ disk_io = psutil.disk_io_counters()
+ disk_read_mb = 0.0
+ disk_write_mb = 0.0
+
+ if disk_io and self._last_disk_io:
+ disk_read_mb = (
+ (disk_io.read_bytes - self._last_disk_io.read_bytes) / 1024 / 1024
+ )
+ disk_write_mb = (
+ (disk_io.write_bytes - self._last_disk_io.write_bytes) / 1024 / 1024
+ )
+
+ self._last_disk_io = disk_io
+
+ # 网络IO
+ network_io = psutil.net_io_counters()
+ network_sent_mb = 0.0
+ network_recv_mb = 0.0
+
+ if network_io and self._last_network_io:
+ network_sent_mb = (
+ (network_io.bytes_sent - self._last_network_io.bytes_sent)
+ / 1024
+ / 1024
+ )
+ network_recv_mb = (
+ (network_io.bytes_recv - self._last_network_io.bytes_recv)
+ / 1024
+ / 1024
+ )
+
+ self._last_network_io = network_io
+
+ # 进程信息
+ process = psutil.Process()
+ active_threads = process.num_threads()
+ open_files = len(process.open_files())
+
+ snapshot = PerformanceSnapshot(
+ timestamp=time.time(),
+ cpu_percent=cpu_percent,
+ memory_percent=memory.percent,
+ memory_used_mb=memory.used / 1024 / 1024,
+ disk_io_read_mb=disk_read_mb,
+ disk_io_write_mb=disk_write_mb,
+ network_sent_mb=network_sent_mb,
+ network_recv_mb=network_recv_mb,
+ active_threads=active_threads,
+ open_files=open_files,
+ )
+
+ with self._lock:
+ self._system_snapshots.append(snapshot)
+
+ return snapshot
+
+ except Exception as e:
+ print(f"收集系统指标失败: {e}")
+ return PerformanceSnapshot(
+ timestamp=time.time(),
+ cpu_percent=0.0,
+ memory_percent=0.0,
+ memory_used_mb=0.0,
+ disk_io_read_mb=0.0,
+ disk_io_write_mb=0.0,
+ network_sent_mb=0.0,
+ network_recv_mb=0.0,
+ active_threads=0,
+ open_files=0,
+ )
+
+ def get_metric_history(self, name: str, limit: int = None) -> List[Dict[str, Any]]:
+ """
+ 获取指标历史 - 委托给统计收集器
+ Get metric history - delegate to stats collector
+
+ Args:
+ name: 指标名称
+ limit: 限制数量
+
+ Returns:
+ List[Dict[str, Any]]: 指标历史
+ """
+ # 从统计收集器获取历史数据
+ history = self._stats_collector.get_history(name, limit)
+ return [
+ {
+ "name": entry.name,
+ "value": entry.value,
+ "timestamp": entry.timestamp,
+ "tags": entry.tags,
+ "category": entry.category,
+ }
+ for entry in history
+ ]
+
+ def get_timer_stats(self, name: str) -> Dict[str, float]:
+ """
+ 获取计时器统计 - 委托给统计收集器
+ Get timer statistics - delegate to stats collector
+
+ Args:
+ name: 计时器名称
+
+ Returns:
+ Dict[str, float]: 统计信息
+ """
+ # 从统计收集器获取直方图统计
+ return self._stats_collector.get_histogram_stats(name)
+
+ # 已删除 _percentile 方法,功能委托给统计收集器
+
+ def get_system_stats(self, minutes: int = 5) -> Dict[str, Any]:
+ """
+ 获取系统统计
+ Get system statistics
+
+ Args:
+ minutes: 统计时间范围(分钟)
+
+ Returns:
+ Dict[str, Any]: 系统统计
+ """
+ with self._lock:
+ if not self._system_snapshots:
+ return {}
+
+ # 过滤指定时间范围内的快照
+ cutoff_time = time.time() - (minutes * 60)
+ recent_snapshots = [
+ s for s in self._system_snapshots if s.timestamp >= cutoff_time
+ ]
+
+ if not recent_snapshots:
+ return {}
+
+ # 计算统计信息
+ cpu_values = [s.cpu_percent for s in recent_snapshots]
+ memory_values = [s.memory_percent for s in recent_snapshots]
+
+ return {
+ "time_range_minutes": minutes,
+ "sample_count": len(recent_snapshots),
+ "cpu": {
+ "min": min(cpu_values),
+ "max": max(cpu_values),
+ "mean": statistics.mean(cpu_values),
+ "current": recent_snapshots[-1].cpu_percent,
+ },
+ "memory": {
+ "min": min(memory_values),
+ "max": max(memory_values),
+ "mean": statistics.mean(memory_values),
+ "current": recent_snapshots[-1].memory_percent,
+ },
+ "latest_snapshot": recent_snapshots[-1],
+ }
+
+ def get_all_metrics_summary(self) -> Dict[str, Any]:
+ """
+ 获取所有指标摘要 - 合并统计收集器和系统监控数据
+ Get all metrics summary - merge stats collector and system monitoring data
+
+ Returns:
+ Dict[str, Any]: 指标摘要
+ """
+ # 从统计收集器获取通用统计
+ stats_summary = self._stats_collector.get_all_stats()
+
+ # 添加系统监控数据
+ summary = {
+ **stats_summary,
+ "system": self.get_system_stats(),
+ "collection_time": time.time(),
+ }
+
+ return summary
+
+
+class PerformanceTimer:
+ """
+ 性能计时器上下文管理器
+ Performance Timer Context Manager
+ """
+
+ def __init__(
+ self, collector: MetricCollector, name: str, tags: Dict[str, str] = None
+ ):
+ """
+ 初始化计时器
+ Initialize timer
+
+ Args:
+ collector: 指标收集器
+ name: 计时器名称
+ tags: 标签
+ """
+ self.collector = collector
+ self.name = name
+ self.tags = tags or {}
+ self.start_time = None
+
+ def __enter__(self):
+ self.start_time = time.perf_counter()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if self.start_time is not None:
+ duration = time.perf_counter() - self.start_time
+ self.collector.record_timer(self.name, duration, self.tags)
+ # 忽略异常参数,不需要处理
+ return False
+
+
+def timer_decorator(
+ collector: MetricCollector, name: str = None, tags: Dict[str, str] = None
+):
+ """
+ 计时器装饰器
+ Timer decorator
+
+ Args:
+ collector: 指标收集器
+ name: 计时器名称
+ tags: 标签
+ """
+
+ def decorator(func: Callable) -> Callable:
+ timer_name = name or f"{func.__module__}.{func.__name__}"
+
+ def wrapper(*args, **kwargs):
+ with PerformanceTimer(collector, timer_name, tags):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return decorator
+
+
+# 使用统一的单例管理器
+from ..core import register_singleton
+
+
+@register_singleton("metric_collector")
+def create_metric_collector() -> MetricCollector:
+ """创建指标收集器实例"""
+ return MetricCollector()
+
+
+def get_metric_collector() -> MetricCollector:
+ """
+ 获取全局指标收集器实例
+ Get global metric collector instance
+
+ Returns:
+ MetricCollector: 指标收集器实例
+ """
+ return create_metric_collector()
diff --git a/src/interactive_feedback_server/plugins/__init__.py b/src/interactive_feedback_server/plugins/__init__.py
new file mode 100644
index 0000000..8cca108
--- /dev/null
+++ b/src/interactive_feedback_server/plugins/__init__.py
@@ -0,0 +1,37 @@
+# interactive_feedback_server/plugins/__init__.py
+
+"""
+插件系统模块
+Plugin System Module
+
+提供插件化架构的核心功能,支持动态加载和热重载。
+Provides core functionality for plugin architecture with dynamic loading and hot reload support.
+"""
+
+from .plugin_interface import (
+ PluginInterface,
+ BasePlugin,
+ PluginMetadata,
+ PluginContext,
+ PluginType,
+ PluginStatus,
+ PluginEventHandler,
+)
+
+from .plugin_manager import PluginManager, get_plugin_manager
+
+__all__ = [
+ # 插件接口
+ "PluginInterface",
+ "BasePlugin",
+ "PluginMetadata",
+ "PluginContext",
+ "PluginType",
+ "PluginStatus",
+ "PluginEventHandler",
+ # 插件管理器
+ "PluginManager",
+ "get_plugin_manager",
+]
+
+__version__ = "3.3.0"
diff --git a/src/interactive_feedback_server/plugins/builtin/__init__.py b/src/interactive_feedback_server/plugins/builtin/__init__.py
new file mode 100644
index 0000000..cc642ff
--- /dev/null
+++ b/src/interactive_feedback_server/plugins/builtin/__init__.py
@@ -0,0 +1,9 @@
+# interactive_feedback_server/plugins/builtin/__init__.py
+
+"""
+内置插件模块
+Built-in Plugins Module
+
+包含系统内置的插件实现
+Contains built-in plugin implementations
+"""
diff --git a/src/interactive_feedback_server/plugins/builtin/enhanced_ai_strategy_plugin.py b/src/interactive_feedback_server/plugins/builtin/enhanced_ai_strategy_plugin.py
new file mode 100644
index 0000000..d1ec429
--- /dev/null
+++ b/src/interactive_feedback_server/plugins/builtin/enhanced_ai_strategy_plugin.py
@@ -0,0 +1,368 @@
+# interactive_feedback_server/plugins/builtin/enhanced_ai_strategy_plugin.py
+
+"""
+增强AI策略插件 - V3.3 架构改进版本
+Enhanced AI Strategy Plugin - V3.3 Architecture Improvement Version
+
+提供增强的AI选项处理策略,作为插件化架构的示例实现。
+Provides enhanced AI option processing strategy as an example implementation of plugin architecture.
+"""
+
+from typing import List, Optional, Dict, Any
+from ..plugin_interface import BasePlugin, PluginMetadata, PluginType, PluginContext
+from ...utils.option_strategy import BaseOptionStrategy, OptionContext, OptionResult
+
+
+class EnhancedAIStrategy(BaseOptionStrategy):
+ """
+ 增强AI策略
+ Enhanced AI Strategy
+
+ 提供比基础AI策略更智能的选项处理
+ Provides more intelligent option processing than basic AI strategy
+ """
+
+ def __init__(self, config: Dict[str, Any] = None):
+ """初始化增强AI策略"""
+ super().__init__(
+ name="enhanced_ai_options",
+ priority=0, # 比基础AI策略优先级更高
+ min_text_length=1,
+ max_options=5,
+ )
+ self.config = config or {}
+
+ # 增强功能配置
+ self.enable_sentiment_analysis = self.config.get(
+ "enable_sentiment_analysis", True
+ )
+ self.enable_context_awareness = self.config.get(
+ "enable_context_awareness", True
+ )
+ self.enable_smart_filtering = self.config.get("enable_smart_filtering", True)
+
+ def is_applicable(self, context: OptionContext) -> bool:
+ """
+ 检查增强AI策略是否适用
+ Check if enhanced AI strategy is applicable
+ """
+ # 基础检查
+ if not super().is_applicable(context):
+ return False
+
+ # 检查是否有AI选项
+ if not context.ai_options:
+ return False
+
+ # 检查AI选项质量
+ if not self._has_quality_ai_options(context.ai_options):
+ return False
+
+ return True
+
+ def parse_options(self, context: OptionContext) -> Optional[OptionResult]:
+ """
+ 使用增强逻辑解析AI选项
+ Parse AI options using enhanced logic
+ """
+ if not context.ai_options:
+ return None
+
+ # 智能过滤和增强
+ enhanced_options = self._enhance_ai_options(context.ai_options, context)
+
+ if not enhanced_options:
+ return None
+
+ # 计算增强置信度
+ confidence = self._calculate_enhanced_confidence(enhanced_options, context)
+
+ return self.create_result(
+ options=enhanced_options,
+ confidence=confidence,
+ should_stop=True,
+ source="enhanced_ai",
+ original_count=len(context.ai_options),
+ enhanced_count=len(enhanced_options),
+ sentiment_analyzed=self.enable_sentiment_analysis,
+ context_aware=self.enable_context_awareness,
+ )
+
+ def _has_quality_ai_options(self, ai_options: List[str]) -> bool:
+ """
+ 检查AI选项质量
+ Check AI options quality
+ """
+ if not ai_options:
+ return False
+
+ valid_count = 0
+ for option in ai_options:
+ if isinstance(option, str) and len(option.strip()) >= 2:
+ valid_count += 1
+
+ # 至少需要一个高质量选项
+ return valid_count > 0
+
+ def _enhance_ai_options(
+ self, ai_options: List[str], context: OptionContext
+ ) -> List[str]:
+ """
+ 增强AI选项
+ Enhance AI options
+ """
+ enhanced = []
+
+ for option in ai_options:
+ if not isinstance(option, str) or not option.strip():
+ continue
+
+ enhanced_option = option.strip()
+
+ # 智能过滤
+ if self.enable_smart_filtering:
+ enhanced_option = self._smart_filter_option(enhanced_option, context)
+ if not enhanced_option:
+ continue
+
+ # 情感分析增强
+ if self.enable_sentiment_analysis:
+ enhanced_option = self._sentiment_enhance_option(
+ enhanced_option, context
+ )
+
+ # 上下文感知增强
+ if self.enable_context_awareness:
+ enhanced_option = self._context_enhance_option(enhanced_option, context)
+
+ if enhanced_option and enhanced_option not in enhanced:
+ enhanced.append(enhanced_option)
+
+ return enhanced[: self.max_options]
+
+ def _smart_filter_option(
+ self, option: str, context: OptionContext
+ ) -> Optional[str]:
+ """
+ 智能过滤选项
+ Smart filter option
+ """
+ # 过滤过短或过长的选项
+ if len(option) < 1 or len(option) > 100:
+ return None
+
+ # 过滤纯数字或纯符号
+ if option.isdigit() or not any(c.isalpha() for c in option):
+ return None
+
+ # 过滤重复词汇
+ words = option.lower().split()
+ if len(words) > 1 and len(set(words)) == 1:
+ return None
+
+ return option
+
+ def _sentiment_enhance_option(self, option: str, context: OptionContext) -> str:
+ """
+ 情感分析增强选项
+ Sentiment analysis enhance option
+ """
+ # 简单的情感分析(实际应用中可以使用更复杂的NLP库)
+ positive_words = ["好", "是", "同意", "确定", "yes", "ok", "good"]
+ negative_words = ["不", "否", "拒绝", "取消", "no", "cancel", "bad"]
+
+ option_lower = option.lower()
+
+ # 根据情感倾向调整选项表达
+ if any(word in option_lower for word in positive_words):
+ # 积极选项,保持原样或稍作优化
+ return option
+ elif any(word in option_lower for word in negative_words):
+ # 消极选项,保持原样
+ return option
+ else:
+ # 中性选项,保持原样
+ return option
+
+ def _context_enhance_option(self, option: str, context: OptionContext) -> str:
+ """
+ 上下文感知增强选项
+ Context-aware enhance option
+ """
+ # 根据输入文本的上下文调整选项
+ text_lower = context.text.lower()
+
+ # 问题类文本的选项优化
+ if any(word in text_lower for word in ["?", "?", "如何", "怎么", "什么"]):
+ # 对于问题,确保选项是回答性的
+ if not any(word in option.lower() for word in ["是", "不", "yes", "no"]):
+ return option
+
+ # 确认类文本的选项优化
+ if any(
+ word in text_lower for word in ["确认", "同意", "继续", "confirm", "agree"]
+ ):
+ # 对于确认类,优先确认/取消选项
+ return option
+
+ return option
+
+ def _calculate_enhanced_confidence(
+ self, options: List[str], context: OptionContext
+ ) -> float:
+ """
+ 计算增强置信度
+ Calculate enhanced confidence
+ """
+ base_confidence = 0.95 # 增强AI策略基础置信度很高
+
+ # 根据增强功能调整置信度
+ enhancement_bonus = 0.0
+ if self.enable_sentiment_analysis:
+ enhancement_bonus += 0.02
+ if self.enable_context_awareness:
+ enhancement_bonus += 0.02
+ if self.enable_smart_filtering:
+ enhancement_bonus += 0.01
+
+ # 根据选项质量调整
+ quality_score = self._assess_enhanced_quality(options, context)
+
+ final_confidence = (base_confidence + enhancement_bonus) * quality_score
+ return min(1.0, max(0.0, final_confidence))
+
+ def _assess_enhanced_quality(
+ self, options: List[str], context: OptionContext
+ ) -> float:
+ """
+ 评估增强选项质量
+ Assess enhanced option quality
+ """
+ if not options:
+ return 0.0
+
+ quality_factors = []
+
+ for option in options:
+ # 长度合理性
+ length_score = 1.0
+ if len(option) < 2:
+ length_score = 0.5
+ elif len(option) > 50:
+ length_score = 0.8
+
+ # 内容丰富性
+ content_score = 1.0
+ if len(option.split()) == 1:
+ content_score = 0.9 # 单词选项稍微降分
+
+ # 上下文相关性
+ context_score = 1.0
+ if context.text:
+ # 简单的相关性检查
+ common_chars = set(option.lower()) & set(context.text.lower())
+ if len(common_chars) > 0:
+ context_score = 1.1 # 有共同字符加分
+
+ option_quality = length_score * content_score * context_score
+ quality_factors.append(min(1.2, option_quality))
+
+ return sum(quality_factors) / len(quality_factors)
+
+
+class EnhancedAIStrategyPlugin(BasePlugin):
+ """
+ 增强AI策略插件
+ Enhanced AI Strategy Plugin
+
+ 将增强AI策略封装为插件
+ Wraps enhanced AI strategy as a plugin
+ """
+
+ def __init__(self, metadata: PluginMetadata):
+ """初始化插件"""
+ super().__init__(metadata)
+ self.strategy: Optional[EnhancedAIStrategy] = None
+
+ def _do_initialize(self) -> bool:
+ """执行插件初始化"""
+ try:
+ # 获取插件配置
+ strategy_config = self.get_config("strategy", {})
+
+ # 创建增强AI策略实例
+ self.strategy = EnhancedAIStrategy(strategy_config)
+
+ return True
+ except Exception as e:
+ print(f"增强AI策略插件初始化失败: {e}")
+ return False
+
+ def _do_activate(self) -> bool:
+ """执行插件激活"""
+ try:
+ if not self.strategy:
+ return False
+
+ # 注册策略到选项解析器
+ from ...utils.option_resolver import get_option_resolver
+
+ resolver = get_option_resolver()
+ resolver.add_strategy(self.strategy)
+
+ return True
+ except Exception as e:
+ print(f"增强AI策略插件激活失败: {e}")
+ return False
+
+ def _do_deactivate(self) -> bool:
+ """执行插件停用"""
+ try:
+ if not self.strategy:
+ return True
+
+ # 从选项解析器移除策略
+ from ...utils.option_resolver import get_option_resolver
+
+ resolver = get_option_resolver()
+ resolver.remove_strategy(self.strategy.name)
+
+ return True
+ except Exception as e:
+ print(f"增强AI策略插件停用失败: {e}")
+ return False
+
+ def _do_cleanup(self) -> bool:
+ """执行插件清理"""
+ try:
+ self.strategy = None
+ return True
+ except Exception as e:
+ print(f"增强AI策略插件清理失败: {e}")
+ return False
+
+ def get_strategy(self) -> Optional[EnhancedAIStrategy]:
+ """获取策略实例"""
+ return self.strategy
+
+
+# 插件工厂函数
+def create_plugin() -> EnhancedAIStrategyPlugin:
+ """
+ 创建插件实例
+ Create plugin instance
+
+ Returns:
+ EnhancedAIStrategyPlugin: 插件实例
+ """
+ metadata = PluginMetadata(
+ name="enhanced_ai_strategy",
+ version="1.0.0",
+ description="增强AI选项策略插件,提供智能选项处理功能",
+ author="Interactive Feedback System",
+ plugin_type=PluginType.OPTION_STRATEGY,
+ dependencies=[],
+ min_system_version="3.3.0",
+ )
+
+ return EnhancedAIStrategyPlugin(metadata)
diff --git a/src/interactive_feedback_server/plugins/plugin_interface.py b/src/interactive_feedback_server/plugins/plugin_interface.py
new file mode 100644
index 0000000..9c3a244
--- /dev/null
+++ b/src/interactive_feedback_server/plugins/plugin_interface.py
@@ -0,0 +1,488 @@
+# interactive_feedback_server/plugins/plugin_interface.py
+
+"""
+插件接口定义 - V3.3 架构改进版本
+Plugin Interface Definition - V3.3 Architecture Improvement Version
+
+定义插件系统的核心接口和基础实现,支持热加载和动态扩展。
+Defines core interfaces and base implementations for plugin system with hot loading and dynamic extension support.
+"""
+
+from abc import ABC, abstractmethod
+from typing import Dict, Any, List, Optional, Callable
+from dataclasses import dataclass
+from enum import Enum
+import threading
+
+
+class PluginType(Enum):
+ """插件类型枚举"""
+
+ OPTION_STRATEGY = "option_strategy" # 选项策略插件
+ TEXT_PROCESSOR = "text_processor" # 文本处理插件
+ RULE_ENGINE = "rule_engine" # 规则引擎插件
+ UI_COMPONENT = "ui_component" # UI组件插件
+ DATA_PROCESSOR = "data_processor" # 数据处理插件
+ INTEGRATION = "integration" # 第三方集成插件
+
+
+class PluginStatus(Enum):
+ """插件状态枚举"""
+
+ UNLOADED = "unloaded" # 未加载
+ LOADING = "loading" # 加载中
+ LOADED = "loaded" # 已加载
+ ACTIVE = "active" # 活跃状态
+ INACTIVE = "inactive" # 非活跃状态
+ ERROR = "error" # 错误状态
+ UNLOADING = "unloading" # 卸载中
+
+
+@dataclass
+class PluginMetadata:
+ """
+ 插件元数据
+ Plugin Metadata
+ """
+
+ name: str # 插件名称
+ version: str # 版本号
+ description: str # 描述
+ author: str # 作者
+ plugin_type: PluginType # 插件类型
+ dependencies: List[str] # 依赖列表
+ min_system_version: str = "3.3.0" # 最小系统版本
+ max_system_version: str = "" # 最大系统版本
+ config_schema: Optional[Dict[str, Any]] = None # 配置模式
+ permissions: List[str] = None # 权限列表
+
+ def __post_init__(self):
+ if self.permissions is None:
+ self.permissions = []
+
+
+@dataclass
+class PluginContext:
+ """
+ 插件上下文
+ Plugin Context
+
+ 提供插件运行时需要的系统信息和服务
+ Provides system information and services needed by plugins at runtime
+ """
+
+ system_version: str # 系统版本
+ config: Dict[str, Any] # 系统配置
+ logger: Optional[Any] = None # 日志记录器
+ event_bus: Optional[Any] = None # 事件总线
+ service_registry: Optional[Dict[str, Any]] = None # 服务注册表
+
+ def __post_init__(self):
+ if self.service_registry is None:
+ self.service_registry = {}
+
+
+class PluginInterface(ABC):
+ """
+ 插件接口抽象基类
+ Plugin Interface Abstract Base Class
+
+ 所有插件必须实现的核心接口
+ Core interface that all plugins must implement
+ """
+
+ def __init__(self, metadata: PluginMetadata):
+ """
+ 初始化插件
+ Initialize plugin
+
+ Args:
+ metadata: 插件元数据
+ """
+ self.metadata = metadata
+ self.status = PluginStatus.UNLOADED
+ self.context: Optional[PluginContext] = None
+ self._lock = threading.RLock()
+
+ # 插件统计
+ self._stats = {
+ "load_time": 0.0,
+ "activation_count": 0,
+ "error_count": 0,
+ "last_error": None,
+ }
+
+ @abstractmethod
+ def initialize(self, context: PluginContext) -> bool:
+ """
+ 初始化插件
+ Initialize plugin
+
+ Args:
+ context: 插件上下文
+
+ Returns:
+ bool: 是否初始化成功
+ """
+ pass
+
+ @abstractmethod
+ def activate(self) -> bool:
+ """
+ 激活插件
+ Activate plugin
+
+ Returns:
+ bool: 是否激活成功
+ """
+ pass
+
+ @abstractmethod
+ def deactivate(self) -> bool:
+ """
+ 停用插件
+ Deactivate plugin
+
+ Returns:
+ bool: 是否停用成功
+ """
+ pass
+
+ @abstractmethod
+ def cleanup(self) -> bool:
+ """
+ 清理插件资源
+ Cleanup plugin resources
+
+ Returns:
+ bool: 是否清理成功
+ """
+ pass
+
+ def get_metadata(self) -> PluginMetadata:
+ """获取插件元数据"""
+ return self.metadata
+
+ def get_status(self) -> PluginStatus:
+ """获取插件状态"""
+ with self._lock:
+ return self.status
+
+ def set_status(self, status: PluginStatus) -> None:
+ """设置插件状态"""
+ with self._lock:
+ self.status = status
+
+ def get_stats(self) -> Dict[str, Any]:
+ """
+ 获取插件统计信息
+ Get plugin statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ with self._lock:
+ return {
+ "name": self.metadata.name,
+ "version": self.metadata.version,
+ "type": self.metadata.plugin_type.value,
+ "status": self.status.value,
+ "load_time": self._stats["load_time"],
+ "activation_count": self._stats["activation_count"],
+ "error_count": self._stats["error_count"],
+ "last_error": self._stats["last_error"],
+ }
+
+ def _record_error(self, error: str) -> None:
+ """记录错误"""
+ with self._lock:
+ self._stats["error_count"] += 1
+ self._stats["last_error"] = error
+ self.status = PluginStatus.ERROR
+
+ def __str__(self) -> str:
+ return f"Plugin({self.metadata.name} v{self.metadata.version}, {self.status.value})"
+
+
+class BasePlugin(PluginInterface):
+ """
+ 基础插件实现
+ Base Plugin Implementation
+
+ 提供插件的通用实现基础
+ Provides common implementation foundation for plugins
+ """
+
+ def __init__(self, metadata: PluginMetadata):
+ """初始化基础插件"""
+ super().__init__(metadata)
+ self._initialized = False
+ self._active = False
+ self._config: Dict[str, Any] = {}
+
+ def initialize(self, context: PluginContext) -> bool:
+ """
+ 初始化插件
+ Initialize plugin
+ """
+ try:
+ with self._lock:
+ if self._initialized:
+ return True
+
+ import time
+
+ start_time = time.time()
+
+ self.set_status(PluginStatus.LOADING)
+ self.context = context
+
+ # 验证依赖
+ if not self._check_dependencies():
+ self._record_error("依赖检查失败")
+ return False
+
+ # 验证系统版本
+ if not self._check_system_version():
+ self._record_error("系统版本不兼容")
+ return False
+
+ # 加载配置
+ self._load_config()
+
+ # 执行自定义初始化
+ if not self._do_initialize():
+ self._record_error("自定义初始化失败")
+ return False
+
+ self._initialized = True
+ self.set_status(PluginStatus.LOADED)
+
+ # 记录加载时间
+ self._stats["load_time"] = time.time() - start_time
+
+ return True
+
+ except Exception as e:
+ self._record_error(f"初始化异常: {e}")
+ return False
+
+ def activate(self) -> bool:
+ """
+ 激活插件
+ Activate plugin
+ """
+ try:
+ with self._lock:
+ if not self._initialized:
+ self._record_error("插件未初始化")
+ return False
+
+ if self._active:
+ return True
+
+ # 执行自定义激活逻辑
+ if not self._do_activate():
+ self._record_error("激活失败")
+ return False
+
+ self._active = True
+ self.set_status(PluginStatus.ACTIVE)
+ self._stats["activation_count"] += 1
+
+ return True
+
+ except Exception as e:
+ self._record_error(f"激活异常: {e}")
+ return False
+
+ def deactivate(self) -> bool:
+ """
+ 停用插件
+ Deactivate plugin
+ """
+ try:
+ with self._lock:
+ if not self._active:
+ return True
+
+ # 执行自定义停用逻辑
+ if not self._do_deactivate():
+ self._record_error("停用失败")
+ return False
+
+ self._active = False
+ self.set_status(PluginStatus.INACTIVE)
+
+ return True
+
+ except Exception as e:
+ self._record_error(f"停用异常: {e}")
+ return False
+
+ def cleanup(self) -> bool:
+ """
+ 清理插件资源
+ Cleanup plugin resources
+ """
+ try:
+ with self._lock:
+ self.set_status(PluginStatus.UNLOADING)
+
+ # 先停用
+ if self._active:
+ self.deactivate()
+
+ # 执行自定义清理逻辑
+ if not self._do_cleanup():
+ self._record_error("清理失败")
+ return False
+
+ self._initialized = False
+ self.set_status(PluginStatus.UNLOADED)
+
+ return True
+
+ except Exception as e:
+ self._record_error(f"清理异常: {e}")
+ return False
+
+ def _check_dependencies(self) -> bool:
+ """检查依赖"""
+ # 基础实现:假设所有依赖都满足
+ # 子类可以重写此方法实现具体的依赖检查
+ return True
+
+ def _check_system_version(self) -> bool:
+ """检查系统版本兼容性"""
+ if not self.context:
+ return False
+
+ system_version = self.context.system_version
+ min_version = self.metadata.min_system_version
+ max_version = self.metadata.max_system_version
+
+ # 简单的版本比较(实际应用中可能需要更复杂的版本比较逻辑)
+ if min_version and system_version < min_version:
+ return False
+
+ if max_version and system_version > max_version:
+ return False
+
+ return True
+
+ def _load_config(self) -> None:
+ """加载插件配置"""
+ if self.context and self.context.config:
+ plugin_config_key = f"plugins.{self.metadata.name}"
+ self._config = self.context.config.get(plugin_config_key, {})
+
+ def get_config(self, key: str, default: Any = None) -> Any:
+ """
+ 获取配置值
+ Get configuration value
+
+ Args:
+ key: 配置键
+ default: 默认值
+
+ Returns:
+ Any: 配置值
+ """
+ return self._config.get(key, default)
+
+ def is_active(self) -> bool:
+ """检查插件是否活跃"""
+ with self._lock:
+ return self._active
+
+ def is_initialized(self) -> bool:
+ """检查插件是否已初始化"""
+ with self._lock:
+ return self._initialized
+
+ # 子类需要实现的方法
+ def _do_initialize(self) -> bool:
+ """执行自定义初始化逻辑"""
+ return True
+
+ def _do_activate(self) -> bool:
+ """执行自定义激活逻辑"""
+ return True
+
+ def _do_deactivate(self) -> bool:
+ """执行自定义停用逻辑"""
+ return True
+
+ def _do_cleanup(self) -> bool:
+ """执行自定义清理逻辑"""
+ return True
+
+
+class PluginEventHandler:
+ """
+ 插件事件处理器
+ Plugin Event Handler
+
+ 处理插件生命周期事件
+ Handles plugin lifecycle events
+ """
+
+ def __init__(self):
+ self._handlers: Dict[str, List[Callable]] = {}
+ self._lock = threading.RLock()
+
+ def register_handler(self, event: str, handler: Callable) -> None:
+ """
+ 注册事件处理器
+ Register event handler
+
+ Args:
+ event: 事件名称
+ handler: 处理函数
+ """
+ with self._lock:
+ if event not in self._handlers:
+ self._handlers[event] = []
+ self._handlers[event].append(handler)
+
+ def unregister_handler(self, event: str, handler: Callable) -> bool:
+ """
+ 注销事件处理器
+ Unregister event handler
+
+ Args:
+ event: 事件名称
+ handler: 处理函数
+
+ Returns:
+ bool: 是否成功注销
+ """
+ with self._lock:
+ if event in self._handlers and handler in self._handlers[event]:
+ self._handlers[event].remove(handler)
+ return True
+ return False
+
+ def emit_event(self, event: str, *args, **kwargs) -> None:
+ """
+ 触发事件
+ Emit event
+
+ Args:
+ event: 事件名称
+ *args: 位置参数
+ **kwargs: 关键字参数
+ """
+ with self._lock:
+ if event in self._handlers:
+ for handler in self._handlers[event]:
+ try:
+ handler(*args, **kwargs)
+ except Exception as e:
+ print(f"事件处理器执行失败 {event}: {e}")
+
+ def get_handlers(self, event: str) -> List[Callable]:
+ """获取事件处理器列表"""
+ with self._lock:
+ return self._handlers.get(event, []).copy()
diff --git a/src/interactive_feedback_server/plugins/plugin_manager.py b/src/interactive_feedback_server/plugins/plugin_manager.py
new file mode 100644
index 0000000..5775195
--- /dev/null
+++ b/src/interactive_feedback_server/plugins/plugin_manager.py
@@ -0,0 +1,590 @@
+# interactive_feedback_server/plugins/plugin_manager.py
+
+"""
+插件管理器 - V3.3 架构改进版本
+Plugin Manager - V3.3 Architecture Improvement Version
+
+提供插件的发现、加载、管理和热重载功能。
+Provides plugin discovery, loading, management and hot reload functionality.
+"""
+
+import os
+import sys
+import importlib
+import importlib.util
+import threading
+from pathlib import Path
+from typing import Dict, List, Optional, Type, Any
+import json
+
+from .plugin_interface import (
+ PluginInterface,
+ PluginMetadata,
+ PluginContext,
+ PluginType,
+ PluginStatus,
+ PluginEventHandler,
+)
+
+
+class PluginManager:
+ """
+ 插件管理器
+ Plugin Manager
+
+ 负责插件的生命周期管理、发现和加载
+ Responsible for plugin lifecycle management, discovery and loading
+ """
+
+ def __init__(self, plugin_dirs: List[str] = None, system_version: str = "3.3.0"):
+ """
+ 初始化插件管理器
+ Initialize plugin manager
+
+ Args:
+ plugin_dirs: 插件目录列表
+ system_version: 系统版本
+ """
+ self.system_version = system_version
+ self.plugin_dirs = plugin_dirs or []
+
+ # 插件存储
+ self._plugins: Dict[str, PluginInterface] = {}
+ self._plugin_modules: Dict[str, Any] = {}
+
+ # 线程安全
+ self._lock = threading.RLock()
+
+ # 事件处理
+ self.event_handler = PluginEventHandler()
+
+ # 管理器统计
+ self._stats = {
+ "total_discovered": 0,
+ "total_loaded": 0,
+ "total_active": 0,
+ "total_errors": 0,
+ "discovery_count": 0,
+ }
+
+ # 默认插件目录
+ self._add_default_plugin_dirs()
+
+ def _add_default_plugin_dirs(self) -> None:
+ """添加默认插件目录"""
+ # 当前项目的插件目录
+ current_dir = Path(__file__).parent
+ default_dirs = [
+ str(current_dir / "builtin"), # 内置插件
+ str(current_dir / "external"), # 外部插件
+ str(current_dir / "user"), # 用户插件
+ ]
+
+ for dir_path in default_dirs:
+ if dir_path not in self.plugin_dirs:
+ self.plugin_dirs.append(dir_path)
+
+ def add_plugin_directory(self, directory: str) -> bool:
+ """
+ 添加插件目录
+ Add plugin directory
+
+ Args:
+ directory: 插件目录路径
+
+ Returns:
+ bool: 是否添加成功
+ """
+ try:
+ dir_path = Path(directory)
+ if dir_path.exists() and dir_path.is_dir():
+ abs_path = str(dir_path.absolute())
+ if abs_path not in self.plugin_dirs:
+ self.plugin_dirs.append(abs_path)
+ return True
+ return False
+ except Exception:
+ return False
+
+ def discover_plugins(self) -> List[Dict[str, Any]]:
+ """
+ 发现插件
+ Discover plugins
+
+ Returns:
+ List[Dict[str, Any]]: 发现的插件信息列表
+ """
+ with self._lock:
+ self._stats["discovery_count"] += 1
+ discovered = []
+
+ for plugin_dir in self.plugin_dirs:
+ try:
+ discovered.extend(self._discover_plugins_in_directory(plugin_dir))
+ except Exception as e:
+ print(f"发现插件失败 {plugin_dir}: {e}")
+
+ self._stats["total_discovered"] = len(discovered)
+ return discovered
+
+ def _discover_plugins_in_directory(self, directory: str) -> List[Dict[str, Any]]:
+ """
+ 在指定目录中发现插件
+ Discover plugins in specified directory
+
+ Args:
+ directory: 目录路径
+
+ Returns:
+ List[Dict[str, Any]]: 发现的插件信息
+ """
+ discovered = []
+ dir_path = Path(directory)
+
+ if not dir_path.exists():
+ return discovered
+
+ # 查找插件文件
+ for item in dir_path.iterdir():
+ if (
+ item.is_file()
+ and item.suffix == ".py"
+ and not item.name.startswith("_")
+ ):
+ # Python文件插件
+ plugin_info = self._analyze_python_plugin(item)
+ if plugin_info:
+ discovered.append(plugin_info)
+ elif item.is_dir() and not item.name.startswith("_"):
+ # 插件包
+ plugin_info = self._analyze_plugin_package(item)
+ if plugin_info:
+ discovered.append(plugin_info)
+
+ return discovered
+
+ def _analyze_python_plugin(self, file_path: Path) -> Optional[Dict[str, Any]]:
+ """
+ 分析Python插件文件
+ Analyze Python plugin file
+
+ Args:
+ file_path: 插件文件路径
+
+ Returns:
+ Optional[Dict[str, Any]]: 插件信息
+ """
+ try:
+ # 读取文件内容查找插件元数据
+ with open(file_path, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ # 简单的元数据提取(实际应用中可能需要更复杂的解析)
+ if "PluginInterface" in content or "BasePlugin" in content:
+ return {
+ "type": "python_file",
+ "path": str(file_path),
+ "name": file_path.stem,
+ "discovered_at": self._get_current_timestamp(),
+ }
+ except Exception:
+ pass
+
+ return None
+
+ def _analyze_plugin_package(self, package_path: Path) -> Optional[Dict[str, Any]]:
+ """
+ 分析插件包
+ Analyze plugin package
+
+ Args:
+ package_path: 插件包路径
+
+ Returns:
+ Optional[Dict[str, Any]]: 插件信息
+ """
+ try:
+ # 查找插件清单文件
+ manifest_file = package_path / "plugin.json"
+ if manifest_file.exists():
+ with open(manifest_file, "r", encoding="utf-8") as f:
+ manifest = json.load(f)
+
+ return {
+ "type": "plugin_package",
+ "path": str(package_path),
+ "name": manifest.get("name", package_path.name),
+ "manifest": manifest,
+ "discovered_at": self._get_current_timestamp(),
+ }
+
+ # 查找__init__.py文件
+ init_file = package_path / "__init__.py"
+ if init_file.exists():
+ return {
+ "type": "python_package",
+ "path": str(package_path),
+ "name": package_path.name,
+ "discovered_at": self._get_current_timestamp(),
+ }
+ except Exception:
+ pass
+
+ return None
+
+ def load_plugin(self, plugin_path: str, plugin_name: str = None) -> bool:
+ """
+ 加载插件
+ Load plugin
+
+ Args:
+ plugin_path: 插件路径
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否加载成功
+ """
+ with self._lock:
+ try:
+ if plugin_name is None:
+ plugin_name = Path(plugin_path).stem
+
+ # 检查是否已加载
+ if plugin_name in self._plugins:
+ return True
+
+ # 加载插件模块
+ plugin_module = self._load_plugin_module(plugin_path, plugin_name)
+ if not plugin_module:
+ return False
+
+ # 查找插件类
+ plugin_class = self._find_plugin_class(plugin_module)
+ if not plugin_class:
+ print(f"未找到插件类: {plugin_name}")
+ return False
+
+ # 创建插件实例
+ plugin_instance = self._create_plugin_instance(
+ plugin_class, plugin_name
+ )
+ if not plugin_instance:
+ return False
+
+ # 初始化插件
+ context = self._create_plugin_context()
+ if not plugin_instance.initialize(context):
+ print(f"插件初始化失败: {plugin_name}")
+ return False
+
+ # 注册插件
+ self._plugins[plugin_name] = plugin_instance
+ self._plugin_modules[plugin_name] = plugin_module
+
+ self._stats["total_loaded"] += 1
+
+ # 触发事件
+ self.event_handler.emit_event(
+ "plugin_loaded", plugin_name, plugin_instance
+ )
+
+ return True
+
+ except Exception as e:
+ print(f"加载插件失败 {plugin_name}: {e}")
+ self._stats["total_errors"] += 1
+ return False
+
+ def _load_plugin_module(self, plugin_path: str, plugin_name: str) -> Optional[Any]:
+ """
+ 加载插件模块
+ Load plugin module
+
+ Args:
+ plugin_path: 插件路径
+ plugin_name: 插件名称
+
+ Returns:
+ Optional[Any]: 插件模块
+ """
+ try:
+ path_obj = Path(plugin_path)
+
+ if path_obj.is_file() and path_obj.suffix == ".py":
+ # 加载Python文件
+ spec = importlib.util.spec_from_file_location(plugin_name, plugin_path)
+ if spec and spec.loader:
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+ elif path_obj.is_dir():
+ # 加载Python包
+ if str(path_obj.parent) not in sys.path:
+ sys.path.insert(0, str(path_obj.parent))
+
+ module = importlib.import_module(path_obj.name)
+ return module
+
+ except Exception as e:
+ print(f"加载插件模块失败 {plugin_name}: {e}")
+
+ return None
+
+ def _find_plugin_class(self, module: Any) -> Optional[Type[PluginInterface]]:
+ """
+ 在模块中查找插件类
+ Find plugin class in module
+
+ Args:
+ module: 插件模块
+
+ Returns:
+ Optional[Type[PluginInterface]]: 插件类
+ """
+ for attr_name in dir(module):
+ attr = getattr(module, attr_name)
+ if (
+ isinstance(attr, type)
+ and issubclass(attr, PluginInterface)
+ and attr != PluginInterface
+ ):
+ return attr
+ return None
+
+ def _create_plugin_instance(
+ self, plugin_class: Type[PluginInterface], plugin_name: str
+ ) -> Optional[PluginInterface]:
+ """
+ 创建插件实例
+ Create plugin instance
+
+ Args:
+ plugin_class: 插件类
+ plugin_name: 插件名称
+
+ Returns:
+ Optional[PluginInterface]: 插件实例
+ """
+ try:
+ # 创建默认元数据(实际应用中应该从插件中读取)
+ metadata = PluginMetadata(
+ name=plugin_name,
+ version="1.0.0",
+ description=f"Plugin: {plugin_name}",
+ author="Unknown",
+ plugin_type=PluginType.INTEGRATION,
+ dependencies=[],
+ )
+
+ return plugin_class(metadata)
+ except Exception as e:
+ print(f"创建插件实例失败 {plugin_name}: {e}")
+ return None
+
+ def _create_plugin_context(self) -> PluginContext:
+ """
+ 创建插件上下文
+ Create plugin context
+
+ Returns:
+ PluginContext: 插件上下文
+ """
+ return PluginContext(
+ system_version=self.system_version, config={}, service_registry={}
+ )
+
+ def unload_plugin(self, plugin_name: str) -> bool:
+ """
+ 卸载插件
+ Unload plugin
+
+ Args:
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否卸载成功
+ """
+ with self._lock:
+ if plugin_name not in self._plugins:
+ return False
+
+ try:
+ plugin = self._plugins[plugin_name]
+
+ # 清理插件
+ if not plugin.cleanup():
+ print(f"插件清理失败: {plugin_name}")
+
+ # 移除插件
+ del self._plugins[plugin_name]
+ if plugin_name in self._plugin_modules:
+ del self._plugin_modules[plugin_name]
+
+ self._stats["total_loaded"] -= 1
+ if plugin.get_status() == PluginStatus.ACTIVE:
+ self._stats["total_active"] -= 1
+
+ # 触发事件
+ self.event_handler.emit_event("plugin_unloaded", plugin_name)
+
+ return True
+
+ except Exception as e:
+ print(f"卸载插件失败 {plugin_name}: {e}")
+ return False
+
+ def activate_plugin(self, plugin_name: str) -> bool:
+ """
+ 激活插件
+ Activate plugin
+
+ Args:
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否激活成功
+ """
+ with self._lock:
+ if plugin_name not in self._plugins:
+ return False
+
+ plugin = self._plugins[plugin_name]
+ if plugin.activate():
+ if plugin.get_status() == PluginStatus.ACTIVE:
+ self._stats["total_active"] += 1
+
+ # 触发事件
+ self.event_handler.emit_event("plugin_activated", plugin_name, plugin)
+ return True
+
+ return False
+
+ def deactivate_plugin(self, plugin_name: str) -> bool:
+ """
+ 停用插件
+ Deactivate plugin
+
+ Args:
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否停用成功
+ """
+ with self._lock:
+ if plugin_name not in self._plugins:
+ return False
+
+ plugin = self._plugins[plugin_name]
+ was_active = plugin.get_status() == PluginStatus.ACTIVE
+
+ if plugin.deactivate():
+ if was_active:
+ self._stats["total_active"] -= 1
+
+ # 触发事件
+ self.event_handler.emit_event("plugin_deactivated", plugin_name, plugin)
+ return True
+
+ return False
+
+ def get_plugin(self, plugin_name: str) -> Optional[PluginInterface]:
+ """
+ 获取插件实例
+ Get plugin instance
+
+ Args:
+ plugin_name: 插件名称
+
+ Returns:
+ Optional[PluginInterface]: 插件实例
+ """
+ with self._lock:
+ return self._plugins.get(plugin_name)
+
+ def get_all_plugins(self) -> Dict[str, PluginInterface]:
+ """
+ 获取所有插件
+ Get all plugins
+
+ Returns:
+ Dict[str, PluginInterface]: 所有插件
+ """
+ with self._lock:
+ return self._plugins.copy()
+
+ def get_plugins_by_type(self, plugin_type: PluginType) -> List[PluginInterface]:
+ """
+ 按类型获取插件
+ Get plugins by type
+
+ Args:
+ plugin_type: 插件类型
+
+ Returns:
+ List[PluginInterface]: 指定类型的插件列表
+ """
+ with self._lock:
+ return [
+ plugin
+ for plugin in self._plugins.values()
+ if plugin.metadata.plugin_type == plugin_type
+ ]
+
+ def get_active_plugins(self) -> List[PluginInterface]:
+ """
+ 获取活跃插件
+ Get active plugins
+
+ Returns:
+ List[PluginInterface]: 活跃插件列表
+ """
+ with self._lock:
+ return [
+ plugin
+ for plugin in self._plugins.values()
+ if plugin.get_status() == PluginStatus.ACTIVE
+ ]
+
+ def get_manager_stats(self) -> Dict[str, Any]:
+ """
+ 获取管理器统计信息
+ Get manager statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ with self._lock:
+ plugin_stats = {}
+ for name, plugin in self._plugins.items():
+ plugin_stats[name] = plugin.get_stats()
+
+ return {
+ "manager_stats": self._stats.copy(),
+ "plugin_directories": self.plugin_dirs.copy(),
+ "system_version": self.system_version,
+ "plugins": plugin_stats,
+ }
+
+ def _get_current_timestamp(self) -> float:
+ """获取当前时间戳"""
+ import time
+
+ return time.time()
+
+
+# 全局插件管理器实例
+_global_plugin_manager: Optional[PluginManager] = None
+
+
+def get_plugin_manager() -> PluginManager:
+ """
+ 获取全局插件管理器实例
+ Get global plugin manager instance
+
+ Returns:
+ PluginManager: 插件管理器实例
+ """
+ global _global_plugin_manager
+ if _global_plugin_manager is None:
+ _global_plugin_manager = PluginManager()
+ return _global_plugin_manager
diff --git a/src/interactive_feedback_server/utils/__init__.py b/src/interactive_feedback_server/utils/__init__.py
new file mode 100644
index 0000000..798ff3d
--- /dev/null
+++ b/src/interactive_feedback_server/utils/__init__.py
@@ -0,0 +1,68 @@
+"""
+Interactive Feedback Server Utils
+
+工具模块,包含配置管理、规则引擎等核心功能。
+Utility modules containing configuration management, rule engine and other core features.
+"""
+
+# 导出主要功能模块
+from .config_manager import (
+ get_config,
+ save_config,
+ validate_config,
+ get_display_mode,
+ get_fallback_options,
+ # V4.1 简化:自定义选项开关
+ get_custom_options_enabled,
+ set_custom_options_enabled,
+)
+from .rule_engine import (
+ resolve_final_options,
+ # V4.0 简化:保留核心选项解析功能
+)
+
+# V3.2 优化:新增配置辅助工具
+from .config_helpers import (
+ safe_get_config,
+ safe_get_feature_states,
+ safe_get_fallback_options,
+ handle_config_error,
+ safe_config_operation,
+)
+
+# V3.2 Day 3 优化:新增文本处理工具 - V4.1 精简版本
+from .text_processor import (
+ fast_normalize_text,
+ fast_extract_keywords,
+ fast_find_match,
+ get_text_processor,
+ get_optimized_matcher,
+ # V4.1 移除:get_text_processing_stats未使用
+)
+
+__all__ = [
+ "get_config",
+ "save_config",
+ "validate_config",
+ "get_display_mode",
+ "get_fallback_options",
+ "filter_valid_options", # 新增:公共过滤函数
+ # V4.1 简化:自定义选项开关
+ "get_custom_options_enabled",
+ "set_custom_options_enabled",
+ # 文本处理工具 - V4.1 精简版本
+ "fast_normalize_text",
+ "fast_extract_keywords",
+ "fast_find_match",
+ "get_text_processor",
+ "get_optimized_matcher",
+ # V4.1 移除:get_text_processing_stats未使用
+ "resolve_final_options",
+ # V4.1 简化:保留核心功能
+ # 配置辅助工具
+ "safe_get_config",
+ "safe_get_feature_states",
+ "safe_get_fallback_options",
+ "handle_config_error",
+ "safe_config_operation",
+]
diff --git a/src/interactive_feedback_server/utils/config_helpers.py b/src/interactive_feedback_server/utils/config_helpers.py
new file mode 100644
index 0000000..74aad65
--- /dev/null
+++ b/src/interactive_feedback_server/utils/config_helpers.py
@@ -0,0 +1,179 @@
+# src/interactive_feedback_server/utils/config_helpers.py
+"""
+配置获取辅助工具 (V3.2 写时复制优化版本)
+Configuration helper utilities (V3.2 Copy-on-Write Optimized Version)
+
+提供统一的配置获取和错误处理逻辑,减少代码重复。
+使用写时复制配置对象优化内存使用和性能。
+
+Provides unified configuration retrieval and error handling logic to reduce code duplication.
+Uses copy-on-write configuration objects to optimize memory usage and performance.
+"""
+
+from typing import Dict, Any, Optional, Tuple, Callable
+from .config_manager import (
+ get_config,
+ get_display_mode,
+ get_fallback_options,
+ get_custom_options_enabled,
+ DEFAULT_CONFIG,
+)
+from .list_optimizer import smart_extend, smart_merge
+
+# 已删除未使用的统一配置加载器导入
+
+
+def safe_get_config() -> Tuple[Dict[str, Any], str]:
+ """
+ 安全获取配置,包含错误处理 (传统版本)
+ Safely get configuration with error handling (legacy version)
+
+ Returns:
+ Tuple[Dict[str, Any], str]: (配置字典, 当前显示模式)
+ """
+ try:
+ config = get_config()
+ current_mode = get_display_mode(config)
+ return config, current_mode
+ except Exception as e:
+ print(f"获取配置失败,使用默认值: {e}")
+ # 使用统一的默认配置
+ return DEFAULT_CONFIG.copy(), DEFAULT_CONFIG["display_mode"]
+
+
+# 已移除 safe_get_cow_config - 使用新的统一配置加载器替代
+
+
+def safe_get_feature_states(
+ config: Optional[Dict[str, Any]] = None,
+) -> Tuple[bool, bool]:
+ """
+ 安全获取功能开关状态 - V4.0 简化版本
+ Safely get feature toggle states - V4.0 Simplified Version
+
+ Args:
+ config: 可选的配置字典,如果为None则自动获取
+
+ Returns:
+ Tuple[bool, bool]: (规则引擎启用状态[已移除,始终False], 自定义选项启用状态)
+ """
+ try:
+ if config is None:
+ config, _ = safe_get_config()
+
+ # V4.0 简化:规则引擎已移除,始终返回False
+ rule_engine_enabled = False
+ custom_options_enabled = get_custom_options_enabled(config)
+ return rule_engine_enabled, custom_options_enabled
+ except Exception as e:
+ print(f"获取功能开关状态失败,使用默认值: {e}")
+ return False, False # 默认都禁用,与DEFAULT_CONFIG保持一致
+
+
+def safe_get_fallback_options(config: Optional[Dict[str, Any]] = None) -> list:
+ """
+ 安全获取后备选项 - 简化版本,直接使用config_manager
+ Safely get fallback options - simplified version using config_manager
+
+ Args:
+ config: 可选的配置字典,如果为None则自动获取
+
+ Returns:
+ list: 后备选项列表
+ """
+ try:
+ # 直接使用config_manager的函数,避免重复逻辑
+ return get_fallback_options(config)
+ except Exception as e:
+ print(f"获取后备选项失败,使用默认值: {e}")
+ from .config_manager import filter_valid_options
+
+ return filter_valid_options(DEFAULT_CONFIG["fallback_options"])
+
+
+def handle_config_error(
+ operation: str, error: Exception, default_value: Any = None
+) -> Any:
+ """
+ 统一的配置错误处理
+ Unified configuration error handling
+
+ Args:
+ operation: 操作描述
+ error: 异常对象
+ default_value: 默认返回值
+
+ Returns:
+ Any: 默认值或None
+ """
+ print(f"{operation}失败: {error}")
+ return default_value
+
+
+def safe_config_operation(
+ operation_func: Callable, operation_name: str, default_value: Any = None
+) -> Any:
+ """
+ 安全执行配置操作的通用函数
+ Generic function to safely execute configuration operations
+
+ Args:
+ operation_func: 要执行的操作函数
+ operation_name: 操作名称(用于错误信息)
+ default_value: 操作失败时的默认返回值
+
+ Returns:
+ Any: 操作结果或默认值
+ """
+ try:
+ return operation_func()
+ except Exception as e:
+ return handle_config_error(operation_name, e, default_value)
+
+
+def merge_config_options(*option_lists: list, remove_duplicates: bool = True) -> list:
+ """
+ 合并多个配置选项列表 (V3.2 优化版本)
+ Merge multiple configuration option lists (V3.2 Optimized Version)
+
+ Args:
+ *option_lists: 要合并的选项列表
+ remove_duplicates: 是否移除重复项
+
+ Returns:
+ list: 合并后的选项列表
+ """
+ return smart_merge(
+ *option_lists, remove_duplicates=remove_duplicates, preserve_order=True
+ )
+
+
+# 已移除 create_config_hierarchy - 使用新的统一配置加载器替代
+
+
+def get_config_stats() -> Dict[str, Any]:
+ """
+ 获取配置系统统计信息 - 简化版本
+ Get configuration system statistics - simplified version
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ try:
+ # 使用主配置管理器的统计信息
+ config = get_config()
+
+ return {
+ "default_config_size": len(DEFAULT_CONFIG),
+ "current_config_size": len(config),
+ "optimization_enabled": True,
+ "list_optimizer_available": True,
+ "version": "V4.1-Simplified",
+ }
+ except Exception as e:
+ return {
+ "error": str(e),
+ "optimization_enabled": False,
+ "list_optimizer_available": False,
+ "version": "V4.1-Error",
+ }
diff --git a/src/interactive_feedback_server/utils/config_manager.py b/src/interactive_feedback_server/utils/config_manager.py
new file mode 100644
index 0000000..d5a4855
--- /dev/null
+++ b/src/interactive_feedback_server/utils/config_manager.py
@@ -0,0 +1,416 @@
+# src/interactive_feedback_server/utils/config_manager.py
+"""
+配置管理器 - V3.2 性能优化版本
+Configuration Manager - V3.2 Performance Optimized Version
+
+V3.2 新增:支持显示模式配置和功能开关
+V3.2 New: Support for display mode configuration and feature toggles
+
+V3.2 性能优化:集成配置缓存机制,显著提升配置获取速度
+V3.2 Performance Optimization: Integrated configuration caching for significant speed improvement
+"""
+
+import os
+import sys
+import json
+from typing import Dict, Any, List
+from datetime import datetime
+
+
+# 配置文件路径 - 简化路径选择
+def _get_config_file_path() -> str:
+ """
+ 简化的配置文件路径选择,支持开发模式和生产模式
+ Simplified config file path selection for development and production modes
+
+ 优先级:
+ 1. 项目根目录 config.json(开发模式优先)
+ 2. 用户主目录 ~/.interactive-feedback/config.json(uvx安装)
+ """
+ # 1. 项目根目录(开发模式优先)
+ try:
+ project_root = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
+ )
+ project_config_path = os.path.join(project_root, "config.json")
+
+ # 如果项目目录可写,使用项目配置
+ if os.access(project_root, os.W_OK):
+ return project_config_path
+ except Exception:
+ pass
+
+ # 2. 用户主目录(uvx安装回退)
+ try:
+ user_config_dir = os.path.expanduser("~/.interactive-feedback")
+ os.makedirs(user_config_dir, exist_ok=True)
+ return os.path.join(user_config_dir, "config.json")
+ except Exception:
+ # 最后的回退选项
+ return os.path.expanduser("~/.interactive-feedback/config.json")
+
+
+CONFIG_FILE_PATH = _get_config_file_path()
+
+# 出厂默认配置 - V4.2 用户友好版本
+DEFAULT_CONFIG = {
+ "display_mode": "full", # V4.2 改为默认完整模式
+ "enable_custom_options": True, # V4.4 修复:默认启用自定义选项,解决uv安装用户看不到预定义选项的问题
+ "submit_method": "enter", # V4.3 新增:提交方式设置 ('enter' 或 'ctrl_enter')
+ "fallback_options": [
+ "好的,我明白了",
+ "请继续",
+ "需要更多信息",
+ "返回上一步",
+ "暂停,让我思考一下",
+ ],
+ "expression_optimizer": {
+ "enabled": True, # V4.2 改为默认启用,提升用户体验
+ "active_provider": "openai",
+ "prompts": {
+ "optimize": "你是一个专业的文本优化助手。请将用户的输入文本改写为结构化、逻辑清晰的指令。只需要输出优化后的文本,不要包含任何技术参数、函数定义或元数据信息。",
+ "reinforce": "你是一个指令执行助手。请严格按照用户提供的'强化指令',对用户提供的'原始文本'进行处理和改写。只输出改写结果,不要包含任何技术信息。",
+ },
+ "performance": {
+ "timeout_seconds": 30,
+ "max_retries": 3,
+ "retry_delay_seconds": 1,
+ "rate_limit_requests_per_minute": 60,
+ },
+ "providers": {}, # 空的提供商配置,用户配置后填充
+ },
+ "version": "3.2",
+ "created_at": datetime.now().isoformat() + "Z",
+ "updated_at": datetime.now().isoformat() + "Z",
+}
+
+
+# 环境变量 API key 配置功能已移除
+# 现在用户只能通过 UI 设置页面管理 API key,避免配置冲突
+
+
+# _merge_env_config 函数已移除,不再支持环境变量配置合并
+
+
+def validate_config(config: Dict[str, Any]) -> bool:
+ """
+ 验证配置文件的有效性
+ Validate configuration file validity
+
+ Args:
+ config: 配置字典
+
+ Returns:
+ bool: 配置是否有效
+ """
+ try:
+ # 检查必需字段
+ if "display_mode" not in config:
+ return False
+ if "fallback_options" not in config:
+ return False
+
+ # V4.0 简化:检查自定义选项控制字段(可选,有默认值)
+ if "enable_custom_options" in config:
+ if not isinstance(config["enable_custom_options"], bool):
+ return False
+
+ # V4.3 新增:验证提交方式字段(可选,有默认值)
+ if "submit_method" in config:
+ if config["submit_method"] not in ["enter", "ctrl_enter"]:
+ return False
+
+ # 验证display_mode值
+ if config["display_mode"] not in ["simple", "full"]:
+ return False
+
+ # 验证fallback_options
+ fallback_options = config["fallback_options"]
+ if not isinstance(fallback_options, list):
+ return False
+ if len(fallback_options) != 5:
+ return False
+
+ # 验证每个选项(允许占位符存在)
+ for option in fallback_options:
+ if not isinstance(option, str):
+ return False
+ if len(option) > 50: # 字符长度限制
+ return False
+ # 允许占位符和空字符串存在,由过滤函数处理
+
+ return True
+
+ except Exception as e:
+ print(f"配置验证异常 (Config validation error): {e}", file=sys.stderr)
+ return False
+
+
+def get_config() -> Dict[str, Any]:
+ """
+ 安全地读取并解析配置文件,与出厂默认值合并
+ Safely read and parse config file, merge with factory defaults
+
+ 配置优先级:配置文件 > 默认配置
+
+ Returns:
+ Dict[str, Any]: 合并后的配置字典
+ """
+ return _load_config_with_fallback()
+
+
+def _load_config_with_fallback() -> Dict[str, Any]:
+ """
+ 简化的配置加载逻辑
+ Simplified config loading logic
+
+ 配置优先级:配置文件 > 默认配置
+
+ Returns:
+ Dict[str, Any]: 配置字典
+ """
+ # 从默认配置开始
+ config = DEFAULT_CONFIG.copy()
+
+ try:
+ # 1. 检查配置文件是否存在
+ if not os.path.exists(CONFIG_FILE_PATH):
+ print(
+ f"配置文件不存在,使用默认配置 (Config file not found, using defaults): {CONFIG_FILE_PATH}",
+ file=sys.stderr,
+ )
+ return config
+
+ # 2. 读取配置文件
+ with open(CONFIG_FILE_PATH, "r", encoding="utf-8") as f:
+ content = f.read().strip()
+ if not content:
+ print(
+ "配置文件为空,使用默认配置 (Config file empty, using defaults)",
+ file=sys.stderr,
+ )
+ return config
+
+ user_config = json.loads(content)
+
+ # 3. 验证用户配置
+ if not validate_config(user_config):
+ print(
+ "配置文件无效,使用默认配置 (Invalid config file, using defaults)",
+ file=sys.stderr,
+ )
+ return config
+
+ # 4. 合并配置:默认配置 <- 文件配置
+ config.update(user_config)
+
+ # 5. 更新时间戳
+ config["updated_at"] = datetime.now().isoformat() + "Z"
+
+ return config
+
+ except json.JSONDecodeError as e:
+ print(
+ f"配置文件JSON解析失败,使用默认配置 (JSON parse error, using defaults): {e}",
+ file=sys.stderr,
+ )
+ return config
+ except Exception as e:
+ print(
+ f"读取配置文件失败,使用默认配置 (Failed to read config, using defaults): {e}",
+ file=sys.stderr,
+ )
+ return config
+
+
+def save_config(config: Dict[str, Any]) -> bool:
+ """
+ 保存配置到文件 (V3.2 缓存优化版本)
+ Save configuration to file (V3.2 Cached Version)
+
+ V3.2 性能优化:
+ - 保存后自动清除缓存,确保下次读取最新配置
+ - 支持缓存失效通知
+
+ Args:
+ config: 要保存的配置字典
+
+ Returns:
+ bool: 保存是否成功
+ """
+ try:
+ # 验证配置
+ if not validate_config(config):
+ print("配置无效,无法保存 (Invalid config, cannot save)", file=sys.stderr)
+ return False
+
+ # 更新时间戳
+ config["updated_at"] = datetime.now().isoformat() + "Z"
+
+ # 确保目录存在
+ config_dir = os.path.dirname(CONFIG_FILE_PATH)
+ if not os.path.exists(config_dir):
+ os.makedirs(config_dir, exist_ok=True)
+
+ # 保存配置文件
+ with open(CONFIG_FILE_PATH, "w", encoding="utf-8") as f:
+ json.dump(config, f, ensure_ascii=False, indent=2)
+
+ print(f"配置已保存 (Config saved): {CONFIG_FILE_PATH}")
+ return True
+
+ except Exception as e:
+ print(f"保存配置失败 (Failed to save config): {e}", file=sys.stderr)
+ return False
+
+
+# 占位符常量定义
+PLACEHOLDER_VALUES = ["请输入选项", "null"]
+
+
+def filter_valid_options(options: List[str]) -> List[str]:
+ """
+ 过滤有效选项,移除占位符和空值
+ Filter valid options, remove placeholders and empty values
+
+ Args:
+ options: 原始选项列表
+
+ Returns:
+ List[str]: 过滤后的有效选项列表
+ """
+ filtered_options = []
+ for option in options:
+ if isinstance(option, str):
+ option = option.strip()
+ if option and option not in PLACEHOLDER_VALUES:
+ filtered_options.append(option)
+ return filtered_options
+
+
+def get_fallback_options(config: Dict[str, Any] = None) -> List[str]:
+ """
+ 获取后备选项列表(过滤空选项)
+ Get fallback options list (filter empty options)
+
+ Args:
+ config: 配置字典,如果为None则自动读取
+
+ Returns:
+ List[str]: 后备选项列表(已过滤空选项)
+ """
+ if config is None:
+ config = get_config()
+
+ options = config.get("fallback_options", DEFAULT_CONFIG["fallback_options"])
+ return filter_valid_options(options)
+
+
+def safe_get_fallback_options(config: Dict[str, Any] = None) -> List[str]:
+ """
+ 安全获取后备选项列表(确保至少有一个有效选项)
+ Safely get fallback options list (ensure at least one valid option)
+
+ Args:
+ config: 配置字典,如果为None则自动读取
+
+ Returns:
+ List[str]: 后备选项列表(确保非空)
+ """
+ options = get_fallback_options(config)
+
+ # 如果没有有效选项,返回默认选项
+ if not options:
+ return DEFAULT_CONFIG["fallback_options"]
+
+ return options
+
+
+def get_display_mode(config: Dict[str, Any] = None) -> str:
+ """
+ 获取显示模式
+ Get display mode
+
+ Args:
+ config: 配置字典,如果为None则自动读取
+
+ Returns:
+ str: 显示模式 ("simple" 或 "full")
+ """
+ if config is None:
+ config = get_config()
+
+ return config.get("display_mode", DEFAULT_CONFIG["display_mode"])
+
+
+# V4.0 移除:get_rule_engine_enabled 函数已删除
+
+
+def get_custom_options_enabled(config: Dict[str, Any] = None) -> bool:
+ """
+ 获取自定义选项启用状态
+ Get custom options enabled status
+
+ Args:
+ config: 配置字典,如果为None则自动读取
+
+ Returns:
+ bool: 是否启用自定义选项
+ """
+ if config is None:
+ config = get_config()
+
+ return get_feature_enabled(
+ config, "enable_custom_options", DEFAULT_CONFIG["enable_custom_options"]
+ )
+
+
+# V4.0 移除:set_rule_engine_enabled 函数已删除
+
+
+def set_custom_options_enabled(enabled: bool) -> bool:
+ """
+ 设置自定义选项启用状态
+ Set custom options enabled status
+
+ Args:
+ enabled: 是否启用自定义选项
+
+ Returns:
+ bool: 设置是否成功
+ """
+ config = get_config()
+ config["enable_custom_options"] = enabled
+ return save_config(config)
+
+
+def get_feature_enabled(
+ config: Dict[str, Any], feature_key: str, default: bool = True
+) -> bool:
+ """
+ 统一的功能启用状态检查工具
+ Unified feature enabled status check utility
+
+ Args:
+ config: 配置字典
+ feature_key: 功能配置键
+ default: 默认值
+
+ Returns:
+ bool: 是否启用该功能
+ """
+ if not config or feature_key not in config:
+ return default
+
+ value = config[feature_key]
+ if isinstance(value, bool):
+ return value
+ elif isinstance(value, str):
+ return value.lower() in ["true", "1", "yes", "on", "enabled"]
+ elif isinstance(value, int):
+ return value != 0
+
+ return default
+
+
+# V4.1 简化:移除复杂的缓存管理函数,使用直接的配置加载逻辑
diff --git a/src/interactive_feedback_server/utils/list_optimizer.py b/src/interactive_feedback_server/utils/list_optimizer.py
new file mode 100644
index 0000000..df4d617
--- /dev/null
+++ b/src/interactive_feedback_server/utils/list_optimizer.py
@@ -0,0 +1,241 @@
+# interactive_feedback_server/utils/list_optimizer.py
+
+"""
+列表操作优化工具
+List Operation Optimization Tools
+
+提供智能的列表操作函数,避免不必要的复制和内存分配,
+优化列表合并、扩展等常见操作的性能。
+
+Provides intelligent list operation functions to avoid unnecessary
+copying and memory allocation, optimizing performance of common
+operations like list merging and extension.
+"""
+
+from typing import List, Any, Optional, TypeVar, Callable
+
+T = TypeVar("T")
+
+
+def smart_extend(target: List[T], source: List[T], in_place: bool = False) -> List[T]:
+ """
+ 智能列表扩展,避免不必要的复制
+ Smart list extension, avoiding unnecessary copying
+
+ Args:
+ target: 目标列表
+ source: 源列表
+ in_place: 是否就地修改目标列表
+
+ Returns:
+ List[T]: 扩展后的列表
+ """
+ if not source:
+ return target if in_place else target.copy()
+
+ if not target:
+ return source.copy()
+
+ if in_place:
+ target.extend(source)
+ return target
+ else:
+ # 根据大小选择最优策略
+ if len(source) == 1:
+ # 单个元素,使用 + 操作符更高效
+ return target + [source[0]]
+ elif len(target) < len(source):
+ # 目标较小,复制目标后扩展
+ result = target.copy()
+ result.extend(source)
+ return result
+ else:
+ # 源较小,使用 + 操作符
+ return target + source
+
+
+def smart_merge(
+ *lists: List[T], remove_duplicates: bool = False, preserve_order: bool = True
+) -> List[T]:
+ """
+ 智能列表合并,优化内存使用
+ Smart list merging, optimizing memory usage
+
+ Args:
+ *lists: 要合并的列表
+ remove_duplicates: 是否移除重复项
+ preserve_order: 是否保持顺序
+
+ Returns:
+ List[T]: 合并后的列表
+ """
+ if not lists:
+ return []
+
+ # 过滤空列表
+ non_empty_lists = [lst for lst in lists if lst]
+
+ if not non_empty_lists:
+ return []
+
+ if len(non_empty_lists) == 1:
+ result = non_empty_lists[0].copy()
+ else:
+ # 直接合并,避免不必要的长度计算
+ result = []
+ for lst in non_empty_lists:
+ result.extend(lst)
+
+ if remove_duplicates:
+ if preserve_order:
+ # 保持顺序的去重
+ seen = set()
+ unique_result = []
+ for item in result:
+ if item not in seen:
+ seen.add(item)
+ unique_result.append(item)
+ return unique_result
+ else:
+ # 不保持顺序,使用set更高效
+ return list(set(result))
+
+ return result
+
+
+def smart_filter(
+ source: List[T], predicate: Callable[[T], bool], in_place: bool = False
+) -> List[T]:
+ """
+ 智能列表过滤,优化内存使用
+ Smart list filtering, optimizing memory usage
+
+ Args:
+ source: 源列表
+ predicate: 过滤条件函数
+ in_place: 是否就地修改(仅当可能时)
+
+ Returns:
+ List[T]: 过滤后的列表
+ """
+ if not source:
+ return []
+
+ if in_place:
+ # 就地过滤(从后往前删除以避免索引问题)
+ for i in range(len(source) - 1, -1, -1):
+ if not predicate(source[i]):
+ del source[i]
+ return source
+ else:
+ # 创建新列表
+ return [item for item in source if predicate(item)]
+
+
+def smart_deduplicate(
+ source: List[T],
+ key_func: Optional[Callable[[T], Any]] = None,
+ preserve_order: bool = True,
+) -> List[T]:
+ """
+ 智能去重,支持自定义键函数
+ Smart deduplication with custom key function support
+
+ Args:
+ source: 源列表
+ key_func: 键提取函数
+ preserve_order: 是否保持顺序
+
+ Returns:
+ List[T]: 去重后的列表
+ """
+ if not source:
+ return []
+
+ if len(source) == 1:
+ return source.copy()
+
+ if key_func is None:
+ # 简单去重
+ if preserve_order:
+ seen = set()
+ result = []
+ for item in source:
+ if item not in seen:
+ seen.add(item)
+ result.append(item)
+ return result
+ else:
+ return list(set(source))
+ else:
+ # 基于键函数的去重
+ seen_keys = set()
+ result = []
+
+ for item in source:
+ key = key_func(item)
+ if key not in seen_keys:
+ seen_keys.add(key)
+ result.append(item)
+
+ return result
+
+
+def smart_partition(
+ source: List[T], predicate: Callable[[T], bool]
+) -> tuple[List[T], List[T]]:
+ """
+ 智能分区,将列表分为满足和不满足条件的两部分
+ Smart partitioning, dividing list into matching and non-matching parts
+
+ Args:
+ source: 源列表
+ predicate: 分区条件函数
+
+ Returns:
+ tuple[List[T], List[T]]: (满足条件的列表, 不满足条件的列表)
+ """
+ if not source:
+ return [], []
+
+ true_items = []
+ false_items = []
+
+ for item in source:
+ if predicate(item):
+ true_items.append(item)
+ else:
+ false_items.append(item)
+
+ return true_items, false_items
+
+
+def smart_chunk(source: List[T], chunk_size: int) -> List[List[T]]:
+ """
+ 智能分块,将列表分割为指定大小的块
+ Smart chunking, dividing list into chunks of specified size
+
+ Args:
+ source: 源列表
+ chunk_size: 块大小
+
+ Returns:
+ List[List[T]]: 分块后的列表
+ """
+ if not source or chunk_size <= 0:
+ return []
+
+ if chunk_size >= len(source):
+ return [source.copy()]
+
+ chunks = []
+ for i in range(0, len(source), chunk_size):
+ chunks.append(source[i : i + chunk_size])
+
+ return chunks
+
+
+# V4.1 移除:LazyList类和create_lazy_list函数未在输入优化功能中使用
+
+
+# V4.1 移除:性能跟踪装饰器未在输入优化功能中使用,简化代码
diff --git a/src/interactive_feedback_server/utils/option_resolver.py b/src/interactive_feedback_server/utils/option_resolver.py
new file mode 100644
index 0000000..56493d3
--- /dev/null
+++ b/src/interactive_feedback_server/utils/option_resolver.py
@@ -0,0 +1,619 @@
+# interactive_feedback_server/utils/option_resolver.py
+
+"""
+选项解析器 - V3.3 架构改进版本
+Option Resolver - V3.3 Architecture Improvement Version
+
+统一的选项解析器,使用策略模式重构三层逻辑。
+Unified option resolver using strategy pattern to refactor three-layer logic.
+"""
+
+from typing import List, Dict, Any, Optional
+from .option_strategy import OptionContext, OptionResult, StrategyChain
+from .option_strategies import (
+ AIOptionsStrategy,
+ FallbackOptionsStrategy,
+)
+from ..monitoring import get_metric_collector, PerformanceTimer
+from ..error_handling import get_error_handler, create_error_context, SystemError
+from ..core import get_stats_collector, increment_stat, register_singleton
+
+
+class OptionResolver:
+ """
+ 选项解析器
+ Option Resolver
+
+ 使用策略链模式实现三层回退逻辑的统一解析
+ Implements unified parsing of three-layer fallback logic using strategy chain pattern
+ """
+
+ def __init__(self):
+ """初始化选项解析器"""
+ self.strategy_chain = StrategyChain()
+ self._setup_default_strategies()
+
+ # 插件系统集成
+ self._plugin_integration_enabled = True
+ self._initialize_plugin_system()
+
+ # 性能监控集成
+ self._monitoring_enabled = True
+ self._initialize_monitoring()
+
+ # 错误处理集成
+ self._error_handling_enabled = True
+ self._initialize_error_handling()
+
+ # 使用统一的统计收集器
+ self.stats_collector = get_stats_collector()
+
+ def _setup_default_strategies(self) -> None:
+ """
+ 设置默认策略链
+ Setup default strategy chain
+ """
+ # V4.0 简化:按优先级添加策略(移除规则引擎)
+ self.strategy_chain.add_strategy(AIOptionsStrategy())
+ self.strategy_chain.add_strategy(FallbackOptionsStrategy())
+
+ def _initialize_plugin_system(self) -> None:
+ """
+ 初始化插件系统
+ Initialize plugin system
+ """
+ if not self._plugin_integration_enabled:
+ return
+
+ try:
+ # 导入插件管理器
+ from ..plugins import get_plugin_manager
+
+ self.plugin_manager = get_plugin_manager()
+
+ # 发现并加载内置插件
+ self._load_builtin_plugins()
+
+ except Exception as e:
+ print(f"插件系统初始化失败: {e}")
+ self._plugin_integration_enabled = False
+
+ def _load_builtin_plugins(self) -> None:
+ """
+ 加载内置插件
+ Load built-in plugins
+ """
+ try:
+ # 发现插件
+ discovered_plugins = self.plugin_manager.discover_plugins()
+
+ # 加载内置插件
+ for plugin_info in discovered_plugins:
+ if "builtin" in plugin_info.get("path", ""):
+ plugin_path = plugin_info["path"]
+ plugin_name = plugin_info["name"]
+
+ if self.plugin_manager.load_plugin(plugin_path, plugin_name):
+ # 激活插件
+ self.plugin_manager.activate_plugin(plugin_name)
+ print(f"已加载内置插件: {plugin_name}")
+
+ except Exception as e:
+ print(f"加载内置插件失败: {e}")
+
+ def _initialize_monitoring(self) -> None:
+ """
+ 初始化性能监控
+ Initialize performance monitoring
+ """
+ if not self._monitoring_enabled:
+ return
+
+ try:
+ # 获取指标收集器
+ self.metric_collector = get_metric_collector()
+
+ # 初始化监控指标
+ self.metric_collector.set_gauge("option_resolver.initialized", 1.0)
+
+ except Exception as e:
+ print(f"性能监控初始化失败: {e}")
+ self._monitoring_enabled = False
+
+ def _initialize_error_handling(self) -> None:
+ """
+ 初始化错误处理
+ Initialize error handling
+ """
+ if not self._error_handling_enabled:
+ return
+
+ try:
+ # 获取错误处理器
+ self.error_handler = get_error_handler()
+
+ # 注册恢复函数
+ self._register_recovery_functions()
+
+ except Exception as e:
+ print(f"错误处理初始化失败: {e}")
+ self._error_handling_enabled = False
+
+ def _register_recovery_functions(self) -> None:
+ """注册恢复函数"""
+ try:
+ from ..error_handling import get_recovery_manager
+
+ recovery_manager = get_recovery_manager()
+
+ # 注册选项解析器恢复函数
+ def recover_option_resolver():
+ """选项解析器恢复函数"""
+ try:
+ # 重新初始化策略链
+ self.strategy_chain = StrategyChain()
+ self._setup_default_strategies()
+ return True
+ except Exception:
+ return False
+
+ recovery_manager.register_recovery_function(
+ component="option_resolver",
+ operation="recover",
+ recovery_function=recover_option_resolver,
+ priority=2,
+ max_attempts=3,
+ timeout=30.0,
+ )
+
+ # 注册健康检查
+ def health_check():
+ """选项解析器健康检查"""
+ try:
+ # 简单的健康检查:测试解析功能
+ test_result = self.resolve_options("健康检查", None, None, "zh_CN")
+ return isinstance(test_result, list)
+ except Exception:
+ return False
+
+ recovery_manager.register_health_check("option_resolver", health_check)
+
+ except Exception as e:
+ print(f"注册恢复函数失败: {e}")
+
+ def resolve_options(
+ self,
+ text: str,
+ ai_options: Optional[List[str]] = None,
+ config: Optional[Dict[str, Any]] = None,
+ language: str = "zh_CN",
+ ) -> List[str]:
+ """
+ 解析选项 - V3.3 策略模式版本
+ Resolve options - V3.3 Strategy Pattern Version
+
+ Args:
+ text: 用户输入文本
+ ai_options: AI提供的选项
+ config: 配置信息
+ language: 语言代码
+
+ Returns:
+ List[str]: 解析出的选项列表
+ """
+ # 性能监控
+ if self._monitoring_enabled and hasattr(self, "metric_collector"):
+ self.metric_collector.increment_counter("option_resolver.resolve_requests")
+ timer = PerformanceTimer(
+ self.metric_collector, "option_resolver.resolve_duration"
+ )
+ timer.__enter__()
+ else:
+ timer = None
+
+ try:
+ # 统一的统计收集
+ self.stats_collector.increment(
+ "total_resolutions", category="option_resolver"
+ )
+
+ # 创建解析上下文
+ context = OptionContext(
+ text=text,
+ ai_options=ai_options,
+ config=config,
+ language=language,
+ metadata={
+ "resolver_version": "V3.3",
+ "timestamp": self._get_current_timestamp(),
+ },
+ )
+
+ # 执行策略链
+ result = self.strategy_chain.execute(context)
+
+ if result and result.options:
+ # 统一的成功统计
+ self.stats_collector.increment(
+ "successful_resolutions", category="option_resolver"
+ )
+ self.stats_collector.increment(
+ f"strategy_usage_{result.strategy_name}", category="option_resolver"
+ )
+
+ # 性能监控(如果启用)
+ if self._monitoring_enabled and hasattr(self, "metric_collector"):
+ self.metric_collector.increment_counter(
+ "option_resolver.successful_resolutions"
+ )
+ self.metric_collector.increment_counter(
+ f"option_resolver.strategy_usage.{result.strategy_name}"
+ )
+
+ return result.options
+
+ # 失败统计
+ self.stats_collector.increment(
+ "failed_resolutions", category="option_resolver"
+ )
+ if self._monitoring_enabled and hasattr(self, "metric_collector"):
+ self.metric_collector.increment_counter(
+ "option_resolver.failed_resolutions"
+ )
+
+ return []
+
+ except Exception as e:
+ # 错误处理
+ if self._error_handling_enabled and hasattr(self, "error_handler"):
+ error_context = create_error_context(
+ component="option_resolver",
+ operation="resolve_options",
+ additional_data={
+ "text_length": len(text) if text else 0,
+ "has_ai_options": bool(ai_options),
+ "language": language,
+ },
+ )
+
+ recovery_result = self.error_handler.handle_error(e, error_context)
+
+ # 如果有恢复结果,尝试使用
+ if recovery_result and recovery_result.get("action") == "fallback":
+ return ["是的", "不是", "需要更多信息"] # 默认选项
+
+ # 如果错误处理失败,返回默认选项
+ return ["是的", "不是", "需要更多信息"]
+
+ finally:
+ # 结束性能计时
+ if timer:
+ timer.__exit__(None, None, None)
+
+ def resolve_with_details(
+ self,
+ text: str,
+ ai_options: Optional[List[str]] = None,
+ config: Optional[Dict[str, Any]] = None,
+ language: str = "zh_CN",
+ ) -> Dict[str, Any]:
+ """
+ 解析选项并返回详细信息
+ Resolve options and return detailed information
+
+ Args:
+ text: 用户输入文本
+ ai_options: AI提供的选项
+ config: 配置信息
+ language: 语言代码
+
+ Returns:
+ Dict[str, Any]: 包含选项和详细信息的字典
+ """
+ self._resolution_stats["total_resolutions"] += 1
+
+ # 创建解析上下文
+ context = OptionContext(
+ text=text,
+ ai_options=ai_options,
+ config=config,
+ language=language,
+ metadata={
+ "resolver_version": "V3.3",
+ "timestamp": self._get_current_timestamp(),
+ "detailed_mode": True,
+ },
+ )
+
+ # 执行策略链
+ result = self.strategy_chain.execute(context)
+
+ if result and result.options:
+ self._resolution_stats["successful_resolutions"] += 1
+
+ # 更新层级使用统计
+ strategy_name = result.strategy_name
+ if strategy_name in self._resolution_stats["layer_usage"]:
+ self._resolution_stats["layer_usage"][strategy_name] += 1
+
+ return {
+ "options": result.options,
+ "strategy_used": result.strategy_name,
+ "confidence": result.confidence,
+ "metadata": result.metadata,
+ "context": {
+ "text_length": len(text),
+ "has_ai_options": bool(ai_options),
+ "has_config": bool(config),
+ "language": language,
+ },
+ "success": True,
+ }
+
+ # 所有策略都失败
+ return {
+ "options": [],
+ "strategy_used": None,
+ "confidence": 0.0,
+ "metadata": {},
+ "context": {
+ "text_length": len(text),
+ "has_ai_options": bool(ai_options),
+ "has_config": bool(config),
+ "language": language,
+ },
+ "success": False,
+ "error": "All strategies failed to generate options",
+ }
+
+ def add_strategy(self, strategy) -> None:
+ """
+ 添加自定义策略
+ Add custom strategy
+
+ Args:
+ strategy: 策略实例
+ """
+ self.strategy_chain.add_strategy(strategy)
+
+ def remove_strategy(self, name: str) -> bool:
+ """
+ 移除策略
+ Remove strategy
+
+ Args:
+ name: 策略名称
+
+ Returns:
+ bool: 是否成功移除
+ """
+ return self.strategy_chain.remove_strategy(name)
+
+ def enable_strategy(self, name: str) -> bool:
+ """
+ 启用策略
+ Enable strategy
+
+ Args:
+ name: 策略名称
+
+ Returns:
+ bool: 是否成功启用
+ """
+ strategy = self.strategy_chain.get_strategy(name)
+ if strategy:
+ strategy.enable()
+ return True
+ return False
+
+ def disable_strategy(self, name: str) -> bool:
+ """
+ 禁用策略
+ Disable strategy
+
+ Args:
+ name: 策略名称
+
+ Returns:
+ bool: 是否成功禁用
+ """
+ strategy = self.strategy_chain.get_strategy(name)
+ if strategy:
+ strategy.disable()
+ return True
+ return False
+
+ def get_strategy_info(self, name: str) -> Optional[Dict[str, Any]]:
+ """
+ 获取策略详细信息
+ Get detailed strategy information
+
+ Args:
+ name: 策略名称
+
+ Returns:
+ Optional[Dict[str, Any]]: 策略信息
+ """
+ strategy = self.strategy_chain.get_strategy(name)
+ if strategy and hasattr(strategy, "get_strategy_info"):
+ return strategy.get_strategy_info()
+ return None
+
+ def get_resolver_stats(self) -> Dict[str, Any]:
+ """
+ 获取解析器统计信息
+ Get resolver statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ # 使用统一的统计收集器
+ resolver_stats = self.stats_collector.get_category_stats("option_resolver")
+ chain_stats = self.strategy_chain.get_chain_stats()
+
+ # 计算成功率
+ total = resolver_stats.get("count", 0)
+ successful = self.stats_collector.get_counter("successful_resolutions")
+ success_rate = (successful / max(total, 1)) * 100
+
+ # 插件统计
+ plugin_stats = {}
+ if self._plugin_integration_enabled and hasattr(self, "plugin_manager"):
+ try:
+ plugin_stats = self.plugin_manager.get_manager_stats()
+ except Exception:
+ plugin_stats = {"error": "Failed to get plugin stats"}
+
+ return {
+ "resolver_stats": {
+ "total_resolutions": total,
+ "successful_resolutions": successful,
+ "failed_resolutions": self.stats_collector.get_counter(
+ "failed_resolutions"
+ ),
+ "success_rate_percent": round(success_rate, 2),
+ "strategy_usage": {
+ "ai_options": self.stats_collector.get_counter(
+ "strategy_usage_ai_options"
+ ),
+ "fallback_options": self.stats_collector.get_counter(
+ "strategy_usage_fallback_options"
+ ),
+ },
+ },
+ "strategy_chain_stats": chain_stats,
+ "plugin_stats": plugin_stats,
+ "plugin_integration_enabled": self._plugin_integration_enabled,
+ "monitoring_enabled": self._monitoring_enabled,
+ "error_handling_enabled": self._error_handling_enabled,
+ "version": "V3.3-Optimized",
+ }
+
+ def load_plugin(self, plugin_path: str, plugin_name: str = None) -> bool:
+ """
+ 加载插件
+ Load plugin
+
+ Args:
+ plugin_path: 插件路径
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否加载成功
+ """
+ if not self._plugin_integration_enabled or not hasattr(self, "plugin_manager"):
+ return False
+
+ return self.plugin_manager.load_plugin(plugin_path, plugin_name)
+
+ def activate_plugin(self, plugin_name: str) -> bool:
+ """
+ 激活插件
+ Activate plugin
+
+ Args:
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否激活成功
+ """
+ if not self._plugin_integration_enabled or not hasattr(self, "plugin_manager"):
+ return False
+
+ return self.plugin_manager.activate_plugin(plugin_name)
+
+ def deactivate_plugin(self, plugin_name: str) -> bool:
+ """
+ 停用插件
+ Deactivate plugin
+
+ Args:
+ plugin_name: 插件名称
+
+ Returns:
+ bool: 是否停用成功
+ """
+ if not self._plugin_integration_enabled or not hasattr(self, "plugin_manager"):
+ return False
+
+ return self.plugin_manager.deactivate_plugin(plugin_name)
+
+ def get_loaded_plugins(self) -> List[str]:
+ """
+ 获取已加载的插件列表
+ Get loaded plugins list
+
+ Returns:
+ List[str]: 插件名称列表
+ """
+ if not self._plugin_integration_enabled or not hasattr(self, "plugin_manager"):
+ return []
+
+ return list(self.plugin_manager.get_all_plugins().keys())
+
+ def reset_stats(self) -> None:
+ """重置所有统计信息"""
+ self.stats_collector.reset_stats("option_resolver")
+ self.strategy_chain.reset_stats()
+
+ def _get_current_timestamp(self) -> float:
+ """获取当前时间戳"""
+ import time
+
+ return time.time()
+
+ def __str__(self) -> str:
+ return f"OptionResolver(strategies={len(self.strategy_chain)}, version=V3.3)"
+
+
+# 使用单例管理器注册选项解析器
+@register_singleton("option_resolver")
+def create_option_resolver() -> OptionResolver:
+ """创建选项解析器实例"""
+ return OptionResolver()
+
+
+def get_option_resolver() -> OptionResolver:
+ """
+ 获取全局选项解析器实例
+ Get global option resolver instance
+
+ Returns:
+ OptionResolver: 选项解析器实例
+ """
+ return create_option_resolver()
+
+
+def resolve_final_options_v3(
+ text: str,
+ ai_options: Optional[List[str]] = None,
+ config: Optional[Dict[str, Any]] = None,
+ language: str = "zh_CN",
+) -> List[str]:
+ """
+ V3.3 版本的选项解析函数
+ V3.3 version of option resolution function
+
+ Args:
+ text: 用户输入文本
+ ai_options: AI提供的选项
+ config: 配置信息
+ language: 语言代码
+
+ Returns:
+ List[str]: 解析出的选项列表
+ """
+ resolver = get_option_resolver()
+ return resolver.resolve_options(text, ai_options, config, language)
+
+
+def get_resolver_stats() -> Dict[str, Any]:
+ """
+ 获取全局解析器统计信息
+ Get global resolver statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ resolver = get_option_resolver()
+ return resolver.get_resolver_stats()
diff --git a/src/interactive_feedback_server/utils/option_strategies/__init__.py b/src/interactive_feedback_server/utils/option_strategies/__init__.py
new file mode 100644
index 0000000..00bb48b
--- /dev/null
+++ b/src/interactive_feedback_server/utils/option_strategies/__init__.py
@@ -0,0 +1,14 @@
+# interactive_feedback_server/utils/option_strategies/__init__.py
+
+"""
+选项策略实现模块 - V4.0 简化版本
+Option Strategy Implementation Module - V4.0 Simplified Version
+
+包含简化的选项解析策略实现(移除规则引擎)
+Contains simplified option parsing strategy implementations (rule engine removed)
+"""
+
+from .ai_options_strategy import AIOptionsStrategy
+from .fallback_options_strategy import FallbackOptionsStrategy
+
+__all__ = ["AIOptionsStrategy", "FallbackOptionsStrategy"]
diff --git a/src/interactive_feedback_server/utils/option_strategies/ai_options_strategy.py b/src/interactive_feedback_server/utils/option_strategies/ai_options_strategy.py
new file mode 100644
index 0000000..0de24e0
--- /dev/null
+++ b/src/interactive_feedback_server/utils/option_strategies/ai_options_strategy.py
@@ -0,0 +1,201 @@
+# interactive_feedback_server/utils/option_strategies/ai_options_strategy.py
+
+"""
+AI选项策略 - V3.3 架构改进版本
+AI Options Strategy - V3.3 Architecture Improvement Version
+
+处理AI提供的选项,作为第一层回退逻辑。
+Handles AI-provided options as the first layer of fallback logic.
+"""
+
+from typing import List, Optional
+from ..option_strategy import BaseOptionStrategy, OptionContext, OptionResult
+
+
+class AIOptionsStrategy(BaseOptionStrategy):
+ """
+ AI选项策略
+ AI Options Strategy
+
+ 处理AI直接提供的选项,优先级最高
+ Handles AI-provided options with highest priority
+ """
+
+ def __init__(self):
+ """初始化AI选项策略"""
+ super().__init__(
+ name="ai_options",
+ priority=1, # 最高优先级
+ min_text_length=1, # AI选项不依赖文本长度
+ max_options=5, # AI可能提供更多选项
+ )
+
+ def is_applicable(self, context: OptionContext) -> bool:
+ """
+ 检查是否有AI提供的选项
+ Check if AI-provided options are available
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ bool: 是否适用
+ """
+ # 检查是否有AI选项
+ if not context.ai_options:
+ return False
+
+ # 检查AI选项是否有效
+ if not isinstance(context.ai_options, list):
+ return False
+
+ # 检查是否有非空选项
+ valid_options = [
+ opt for opt in context.ai_options if isinstance(opt, str) and opt.strip()
+ ]
+
+ return len(valid_options) > 0
+
+ def parse_options(self, context: OptionContext) -> Optional[OptionResult]:
+ """
+ 解析AI提供的选项
+ Parse AI-provided options
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ Optional[OptionResult]: 解析结果
+ """
+ if not context.ai_options:
+ return None
+
+ # 过滤和清理AI选项
+ valid_options = []
+ for option in context.ai_options:
+ if isinstance(option, str) and option.strip():
+ clean_option = option.strip()
+ if clean_option not in valid_options: # 去重
+ valid_options.append(clean_option)
+
+ if not valid_options:
+ return None
+
+ # 计算置信度(基于选项质量)
+ confidence = self._calculate_confidence(valid_options, context)
+
+ return self.create_result(
+ options=valid_options,
+ confidence=confidence,
+ should_stop=True, # AI选项通常应该停止后续策略
+ source="ai",
+ original_count=len(context.ai_options),
+ filtered_count=len(valid_options),
+ )
+
+ def _calculate_confidence(
+ self, options: List[str], context: OptionContext
+ ) -> float:
+ """
+ 计算AI选项的置信度
+ Calculate confidence of AI options
+
+ Args:
+ options: 有效选项列表
+ context: 选项解析上下文
+
+ Returns:
+ float: 置信度 (0.0-1.0)
+ """
+ base_confidence = 0.9 # AI选项基础置信度较高
+
+ # 根据选项数量调整
+ if len(options) == 0:
+ return 0.0
+ elif len(options) == 1:
+ confidence = base_confidence * 0.8 # 单选项置信度稍低
+ elif len(options) <= 3:
+ confidence = base_confidence # 2-3个选项置信度最高
+ else:
+ confidence = base_confidence * 0.9 # 过多选项置信度稍低
+
+ # 根据选项质量调整
+ quality_score = self._assess_option_quality(options)
+ confidence *= quality_score
+
+ return min(1.0, max(0.0, confidence))
+
+ def _assess_option_quality(self, options: List[str]) -> float:
+ """
+ 评估选项质量
+ Assess option quality
+
+ Args:
+ options: 选项列表
+
+ Returns:
+ float: 质量分数 (0.0-1.0)
+ """
+ if not options:
+ return 0.0
+
+ quality_factors = []
+
+ for option in options:
+ # 长度合理性 (2-50字符)
+ length_score = 1.0
+ if len(option) < 2:
+ length_score = 0.3
+ elif len(option) > 50:
+ length_score = 0.7
+
+ # 内容合理性(不全是标点符号或数字)
+ content_score = 1.0
+ if (
+ option.replace(" ", "")
+ .replace(".", "")
+ .replace("?", "")
+ .replace("!", "")
+ .isdigit()
+ ):
+ content_score = 0.5
+ elif not any(c.isalpha() for c in option):
+ content_score = 0.6
+
+ # 常见回复模式检测
+ common_patterns = ["是", "否", "好的", "取消", "yes", "no", "ok", "cancel"]
+ pattern_score = 1.0
+ if option.lower().strip() in [p.lower() for p in common_patterns]:
+ pattern_score = 1.2 # 常见模式加分
+
+ option_quality = length_score * content_score * pattern_score
+ quality_factors.append(min(1.0, option_quality))
+
+ # 返回平均质量分数
+ return sum(quality_factors) / len(quality_factors)
+
+ def get_strategy_info(self) -> dict:
+ """
+ 获取策略详细信息
+ Get detailed strategy information
+
+ Returns:
+ dict: 策略信息
+ """
+ return {
+ "name": self.name,
+ "description": "AI选项策略 - 处理AI直接提供的选项",
+ "priority": self.priority,
+ "layer": 1,
+ "features": [
+ "优先级最高",
+ "智能置信度计算",
+ "选项质量评估",
+ "自动去重和清理",
+ ],
+ "applicable_when": [
+ "AI提供了有效选项",
+ "选项列表非空",
+ "至少包含一个有效字符串",
+ ],
+ }
diff --git a/src/interactive_feedback_server/utils/option_strategies/fallback_options_strategy.py b/src/interactive_feedback_server/utils/option_strategies/fallback_options_strategy.py
new file mode 100644
index 0000000..2eb47d4
--- /dev/null
+++ b/src/interactive_feedback_server/utils/option_strategies/fallback_options_strategy.py
@@ -0,0 +1,343 @@
+# interactive_feedback_server/utils/option_strategies/fallback_options_strategy.py
+
+"""
+后备选项策略 - V3.3 架构改进版本
+Fallback Options Strategy - V3.3 Architecture Improvement Version
+
+提供用户配置的后备选项,作为第三层回退逻辑。
+Provides user-configured fallback options as the third layer of fallback logic.
+"""
+
+from typing import List, Optional
+from ..option_strategy import BaseOptionStrategy, OptionContext, OptionResult
+
+
+class FallbackOptionsStrategy(BaseOptionStrategy):
+ """
+ 后备选项策略
+ Fallback Options Strategy
+
+ 使用用户配置的后备选项作为最后的选择
+ Uses user-configured fallback options as the last resort
+ """
+
+ def __init__(self):
+ """初始化后备选项策略"""
+ super().__init__(
+ name="fallback_options",
+ priority=3, # 最低优先级
+ min_text_length=0, # 后备选项不依赖文本内容
+ max_options=5, # 后备选项可能较多
+ )
+
+ # 默认后备选项
+ self._default_fallback_options = {
+ "zh_CN": ["是的", "不是", "需要更多信息"],
+ "en_US": ["Yes", "No", "Need more info"],
+ }
+
+ def is_applicable(self, context: OptionContext) -> bool:
+ """
+ 检查后备选项策略是否适用
+ Check if fallback options strategy is applicable
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ bool: 是否适用
+ """
+ # 检查配置是否启用自定义选项
+ if context.config:
+ custom_options_enabled = self._get_custom_options_enabled(context.config)
+ if not custom_options_enabled:
+ return False
+
+ # 后备选项作为最后的保障,在启用时总是适用
+ return True
+
+ def parse_options(self, context: OptionContext) -> Optional[OptionResult]:
+ """
+ 解析后备选项
+ Parse fallback options
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ Optional[OptionResult]: 解析结果
+ """
+ # 尝试从配置获取后备选项
+ fallback_options = self._get_fallback_options_from_config(context)
+
+ # 如果配置中没有,使用默认选项
+ if not fallback_options:
+ fallback_options = self._get_default_fallback_options(context.language)
+
+ if not fallback_options:
+ return None
+
+ # 计算置信度
+ confidence = self._calculate_confidence(fallback_options, context)
+
+ return self.create_result(
+ options=fallback_options,
+ confidence=confidence,
+ should_stop=True, # 后备选项是最后一层,必须停止
+ source="fallback",
+ language=context.language,
+ from_config=context.config is not None,
+ is_default=not bool(
+ context.config and self._has_custom_fallback_options(context.config)
+ ),
+ )
+
+ def _get_fallback_options_from_config(self, context: OptionContext) -> List[str]:
+ """
+ 从配置中获取后备选项
+ Get fallback options from configuration
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ List[str]: 后备选项列表
+ """
+ if not context.config:
+ return []
+
+ # 简化:只检查标准的fallback_options配置键
+ if "fallback_options" in context.config:
+ options = context.config["fallback_options"]
+ if isinstance(options, list):
+ # 使用公共过滤函数
+ from ..config_manager import filter_valid_options
+
+ valid_options = filter_valid_options(options)
+ if valid_options:
+ return valid_options
+
+ return []
+
+ def _get_custom_options_enabled(self, config: dict) -> bool:
+ """
+ 检查配置中是否启用自定义选项
+ Check if custom options are enabled in configuration
+
+ Args:
+ config: 配置字典
+
+ Returns:
+ bool: 是否启用自定义选项
+ """
+ # 使用统一的配置检查工具
+ try:
+ from ..config_manager import get_feature_enabled
+
+ return get_feature_enabled(config, "enable_custom_options", False)
+ except ImportError:
+ # 回退到本地实现
+ if "enable_custom_options" in config:
+ value = config["enable_custom_options"]
+ if isinstance(value, bool):
+ return value
+ elif isinstance(value, str):
+ return value.lower() in ["true", "1", "yes", "on", "enabled"]
+ elif isinstance(value, int):
+ return value != 0
+ return False
+
+ def _has_custom_fallback_options(self, config: dict) -> bool:
+ """
+ 检查配置中是否有自定义后备选项
+ Check if configuration has custom fallback options
+
+ Args:
+ config: 配置字典
+
+ Returns:
+ bool: 是否有自定义选项
+ """
+ fallback_keys = [
+ "fallback_options",
+ "default_options",
+ "backup_options",
+ "last_resort_options",
+ ]
+
+ for key in fallback_keys:
+ if key in config and isinstance(config[key], list) and config[key]:
+ return True
+
+ return False
+
+ def _get_default_fallback_options(self, language: str) -> List[str]:
+ """
+ 获取默认后备选项
+ Get default fallback options
+
+ Args:
+ language: 语言代码
+
+ Returns:
+ List[str]: 默认后备选项
+ """
+ if language in self._default_fallback_options:
+ return self._default_fallback_options[language].copy()
+
+ # 如果不支持该语言,回退到中文
+ return self._default_fallback_options["zh_CN"].copy()
+
+ def _calculate_confidence(
+ self, options: List[str], context: OptionContext
+ ) -> float:
+ """
+ 计算后备选项的置信度
+ Calculate confidence of fallback options
+
+ Args:
+ options: 选项列表
+ context: 选项解析上下文
+
+ Returns:
+ float: 置信度 (0.0-1.0)
+ """
+ if not options:
+ return 0.0
+
+ # 后备选项的基础置信度较低
+ base_confidence = 0.5
+
+ # 如果是用户自定义的后备选项,置信度稍高
+ if context.config and self._has_custom_fallback_options(context.config):
+ base_confidence = 0.6
+
+ # 根据选项数量调整
+ if len(options) == 1:
+ confidence = base_confidence * 0.8
+ elif len(options) <= 3:
+ confidence = base_confidence
+ else:
+ confidence = base_confidence * 0.9
+
+ # 根据选项质量调整
+ quality_score = self._assess_option_quality(options)
+ confidence *= quality_score
+
+ return min(1.0, max(0.1, confidence)) # 最低保证0.1的置信度
+
+ def _assess_option_quality(self, options: List[str]) -> float:
+ """
+ 评估后备选项质量
+ Assess fallback option quality
+
+ Args:
+ options: 选项列表
+
+ Returns:
+ float: 质量分数 (0.0-1.0)
+ """
+ if not options:
+ return 0.0
+
+ quality_factors = []
+
+ for option in options:
+ # 长度合理性
+ length_score = 1.0
+ if len(option) < 1:
+ length_score = 0.1
+ elif len(option) > 30:
+ length_score = 0.8
+
+ # 内容有效性
+ content_score = 1.0
+ if not option.strip():
+ content_score = 0.1
+ elif option.strip() in ["", " ", " "]: # 空白字符
+ content_score = 0.1
+
+ # 常见有效回复检测
+ common_valid = [
+ "是",
+ "否",
+ "是的",
+ "不是",
+ "好的",
+ "取消",
+ "确定",
+ "需要更多信息",
+ "yes",
+ "no",
+ "ok",
+ "cancel",
+ "confirm",
+ "need more info",
+ ]
+ if option.lower().strip() in [p.lower() for p in common_valid]:
+ content_score = 1.2 # 常见有效回复加分
+
+ option_quality = length_score * content_score
+ quality_factors.append(min(1.0, option_quality))
+
+ return sum(quality_factors) / len(quality_factors)
+
+ def add_default_language(self, language: str, options: List[str]) -> None:
+ """
+ 添加新语言的默认后备选项
+ Add default fallback options for new language
+
+ Args:
+ language: 语言代码
+ options: 选项列表
+ """
+ if options and all(isinstance(opt, str) and opt.strip() for opt in options):
+ self._default_fallback_options[language] = [opt.strip() for opt in options]
+
+ def get_supported_languages(self) -> List[str]:
+ """
+ 获取支持的语言列表
+ Get supported language list
+
+ Returns:
+ List[str]: 语言代码列表
+ """
+ return list(self._default_fallback_options.keys())
+
+ def get_default_options_for_language(self, language: str) -> List[str]:
+ """
+ 获取指定语言的默认选项
+ Get default options for specified language
+
+ Args:
+ language: 语言代码
+
+ Returns:
+ List[str]: 默认选项列表
+ """
+ return self._get_default_fallback_options(language)
+
+ def get_strategy_info(self) -> dict:
+ """
+ 获取策略详细信息
+ Get detailed strategy information
+
+ Returns:
+ dict: 策略信息
+ """
+ return {
+ "name": self.name,
+ "description": "后备选项策略 - 提供用户配置的后备选项",
+ "priority": self.priority,
+ "layer": 3,
+ "features": [
+ "总是可用",
+ "支持用户自定义",
+ "多语言默认选项",
+ "质量评估",
+ "最低置信度保证",
+ ],
+ "applicable_when": ["总是适用(最后保障)"],
+ "supported_languages": self.get_supported_languages(),
+ "default_options": self._default_fallback_options,
+ }
diff --git a/src/interactive_feedback_server/utils/option_strategy.py b/src/interactive_feedback_server/utils/option_strategy.py
new file mode 100644
index 0000000..d2c6861
--- /dev/null
+++ b/src/interactive_feedback_server/utils/option_strategy.py
@@ -0,0 +1,442 @@
+# interactive_feedback_server/utils/option_strategy.py
+
+"""
+选项策略接口 - V3.3 架构改进版本
+Option Strategy Interface - V3.3 Architecture Improvement Version
+
+定义统一的选项解析策略接口,实现策略模式重构三层逻辑。
+Defines unified option parsing strategy interface, implementing strategy pattern to refactor three-layer logic.
+"""
+
+from abc import ABC, abstractmethod
+from typing import List, Dict, Any, Optional, Tuple
+from dataclasses import dataclass
+
+
+@dataclass
+class OptionContext:
+ """
+ 选项解析上下文
+ Option parsing context
+
+ 包含解析过程中需要的所有信息
+ Contains all information needed during parsing process
+ """
+
+ text: str # 用户输入文本
+ ai_options: Optional[List[str]] = None # AI提供的选项
+ config: Optional[Dict[str, Any]] = None # 配置信息
+ language: str = "zh_CN" # 语言代码
+ metadata: Optional[Dict[str, Any]] = None # 额外元数据
+
+ def __post_init__(self):
+ """初始化后处理"""
+ if self.metadata is None:
+ self.metadata = {}
+
+
+@dataclass
+class OptionResult:
+ """
+ 选项解析结果
+ Option parsing result
+
+ 包含解析结果和相关信息
+ Contains parsing result and related information
+ """
+
+ options: List[str] # 解析出的选项
+ strategy_name: str # 使用的策略名称
+ confidence: float = 1.0 # 置信度 (0.0-1.0)
+ metadata: Optional[Dict[str, Any]] = None # 结果元数据
+ should_stop: bool = True # 是否应该停止后续策略
+
+ def __post_init__(self):
+ """初始化后处理"""
+ if self.metadata is None:
+ self.metadata = {}
+
+
+class OptionStrategy(ABC):
+ """
+ 选项策略抽象基类
+ Option Strategy Abstract Base Class
+
+ 定义选项解析策略的统一接口
+ Defines unified interface for option parsing strategies
+ """
+
+ def __init__(self, name: str, priority: int = 10):
+ """
+ 初始化策略
+ Initialize strategy
+
+ Args:
+ name: 策略名称
+ priority: 优先级(数字越小优先级越高)
+ """
+ self.name = name
+ self.priority = priority
+ self._enabled = True
+ self._stats = {"call_count": 0, "success_count": 0, "error_count": 0}
+
+ @abstractmethod
+ def is_applicable(self, context: OptionContext) -> bool:
+ """
+ 检查策略是否适用于当前上下文
+ Check if strategy is applicable to current context
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ bool: 是否适用
+ """
+ pass
+
+ @abstractmethod
+ def parse_options(self, context: OptionContext) -> Optional[OptionResult]:
+ """
+ 解析选项
+ Parse options
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ Optional[OptionResult]: 解析结果,如果无法解析则返回None
+ """
+ pass
+
+ def execute(self, context: OptionContext) -> Optional[OptionResult]:
+ """
+ 执行策略(包含统计和错误处理)
+ Execute strategy (with statistics and error handling)
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ Optional[OptionResult]: 解析结果
+ """
+ if not self._enabled:
+ return None
+
+ self._stats["call_count"] += 1
+
+ try:
+ # 检查适用性
+ if not self.is_applicable(context):
+ return None
+
+ # 执行解析
+ result = self.parse_options(context)
+
+ if result and result.options:
+ self._stats["success_count"] += 1
+ return result
+
+ return None
+
+ except Exception as e:
+ self._stats["error_count"] += 1
+ print(f"策略 {self.name} 执行失败: {e}")
+ return None
+
+ def enable(self) -> None:
+ """启用策略"""
+ self._enabled = True
+
+ def disable(self) -> None:
+ """禁用策略"""
+ self._enabled = False
+
+ def is_enabled(self) -> bool:
+ """检查策略是否启用"""
+ return self._enabled
+
+ def get_stats(self) -> Dict[str, Any]:
+ """
+ 获取策略统计信息
+ Get strategy statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ success_rate = 0.0
+ if self._stats["call_count"] > 0:
+ success_rate = (
+ self._stats["success_count"] / self._stats["call_count"]
+ ) * 100
+
+ return {
+ "name": self.name,
+ "priority": self.priority,
+ "enabled": self._enabled,
+ "call_count": self._stats["call_count"],
+ "success_count": self._stats["success_count"],
+ "error_count": self._stats["error_count"],
+ "success_rate_percent": round(success_rate, 2),
+ }
+
+ def reset_stats(self) -> None:
+ """重置统计信息"""
+ self._stats = {"call_count": 0, "success_count": 0, "error_count": 0}
+
+ def __str__(self) -> str:
+ return f"OptionStrategy(name={self.name}, priority={self.priority}, enabled={self._enabled})"
+
+ def __repr__(self) -> str:
+ return self.__str__()
+
+
+class BaseOptionStrategy(OptionStrategy):
+ """
+ 基础选项策略实现
+ Base Option Strategy Implementation
+
+ 提供通用的策略实现基础
+ Provides common strategy implementation foundation
+ """
+
+ def __init__(
+ self,
+ name: str,
+ priority: int = 10,
+ min_text_length: int = 2,
+ max_options: int = 3,
+ ):
+ """
+ 初始化基础策略
+ Initialize base strategy
+
+ Args:
+ name: 策略名称
+ priority: 优先级
+ min_text_length: 最小文本长度
+ max_options: 最大选项数量
+ """
+ super().__init__(name, priority)
+ self.min_text_length = min_text_length
+ self.max_options = max_options
+
+ def is_applicable(self, context: OptionContext) -> bool:
+ """
+ 基础适用性检查
+ Basic applicability check
+ """
+ # 检查文本有效性
+ if not context.text or not isinstance(context.text, str):
+ return False
+
+ # 检查文本长度
+ if len(context.text.strip()) < self.min_text_length:
+ return False
+
+ return True
+
+ def validate_options(self, options: List[str]) -> List[str]:
+ """
+ 验证和清理选项列表
+ Validate and clean options list
+
+ Args:
+ options: 原始选项列表
+
+ Returns:
+ List[str]: 清理后的选项列表
+ """
+ if not options:
+ return []
+
+ # 去重并保持顺序
+ seen = set()
+ cleaned_options = []
+
+ for option in options:
+ if isinstance(option, str) and option.strip():
+ clean_option = option.strip()
+ if clean_option not in seen:
+ seen.add(clean_option)
+ cleaned_options.append(clean_option)
+
+ # 限制数量
+ return cleaned_options[: self.max_options]
+
+ def create_result(
+ self,
+ options: List[str],
+ confidence: float = 1.0,
+ should_stop: bool = True,
+ **metadata,
+ ) -> OptionResult:
+ """
+ 创建选项结果
+ Create option result
+
+ Args:
+ options: 选项列表
+ confidence: 置信度
+ should_stop: 是否停止后续策略
+ **metadata: 额外元数据
+
+ Returns:
+ OptionResult: 选项结果
+ """
+ validated_options = self.validate_options(options)
+
+ return OptionResult(
+ options=validated_options,
+ strategy_name=self.name,
+ confidence=confidence,
+ should_stop=should_stop,
+ metadata=metadata,
+ )
+
+
+class StrategyChain:
+ """
+ 策略链管理器
+ Strategy Chain Manager
+
+ 管理多个策略的执行顺序和结果合并
+ Manages execution order and result merging of multiple strategies
+ """
+
+ def __init__(self):
+ """初始化策略链"""
+ self.strategies: List[OptionStrategy] = []
+ self._execution_stats = {
+ "total_executions": 0,
+ "successful_executions": 0,
+ "strategy_usage": {},
+ }
+
+ def add_strategy(self, strategy: OptionStrategy) -> None:
+ """
+ 添加策略到链中
+ Add strategy to chain
+
+ Args:
+ strategy: 要添加的策略
+ """
+ self.strategies.append(strategy)
+ # 按优先级排序
+ self.strategies.sort(key=lambda s: s.priority)
+
+ def remove_strategy(self, name: str) -> bool:
+ """
+ 从链中移除策略
+ Remove strategy from chain
+
+ Args:
+ name: 策略名称
+
+ Returns:
+ bool: 是否成功移除
+ """
+ for i, strategy in enumerate(self.strategies):
+ if strategy.name == name:
+ del self.strategies[i]
+ return True
+ return False
+
+ def get_strategy(self, name: str) -> Optional[OptionStrategy]:
+ """
+ 获取指定名称的策略
+ Get strategy by name
+
+ Args:
+ name: 策略名称
+
+ Returns:
+ Optional[OptionStrategy]: 策略实例
+ """
+ for strategy in self.strategies:
+ if strategy.name == name:
+ return strategy
+ return None
+
+ def execute(self, context: OptionContext) -> Optional[OptionResult]:
+ """
+ 执行策略链
+ Execute strategy chain
+
+ Args:
+ context: 选项解析上下文
+
+ Returns:
+ Optional[OptionResult]: 第一个成功的策略结果
+ """
+ self._execution_stats["total_executions"] += 1
+
+ for strategy in self.strategies:
+ if not strategy.is_enabled():
+ continue
+
+ result = strategy.execute(context)
+
+ if result and result.options:
+ # 更新使用统计
+ strategy_name = strategy.name
+ if strategy_name not in self._execution_stats["strategy_usage"]:
+ self._execution_stats["strategy_usage"][strategy_name] = 0
+ self._execution_stats["strategy_usage"][strategy_name] += 1
+
+ self._execution_stats["successful_executions"] += 1
+
+ # 如果策略要求停止,则返回结果
+ if result.should_stop:
+ return result
+
+ return None
+
+ def get_chain_stats(self) -> Dict[str, Any]:
+ """
+ 获取策略链统计信息
+ Get strategy chain statistics
+
+ Returns:
+ Dict[str, Any]: 统计信息
+ """
+ success_rate = 0.0
+ if self._execution_stats["total_executions"] > 0:
+ success_rate = (
+ self._execution_stats["successful_executions"]
+ / self._execution_stats["total_executions"]
+ ) * 100
+
+ strategy_stats = [strategy.get_stats() for strategy in self.strategies]
+
+ return {
+ "total_strategies": len(self.strategies),
+ "enabled_strategies": len([s for s in self.strategies if s.is_enabled()]),
+ "total_executions": self._execution_stats["total_executions"],
+ "successful_executions": self._execution_stats["successful_executions"],
+ "success_rate_percent": round(success_rate, 2),
+ "strategy_usage": self._execution_stats["strategy_usage"],
+ "strategies": strategy_stats,
+ }
+
+ def reset_stats(self) -> None:
+ """重置所有统计信息"""
+ self._execution_stats = {
+ "total_executions": 0,
+ "successful_executions": 0,
+ "strategy_usage": {},
+ }
+
+ for strategy in self.strategies:
+ strategy.reset_stats()
+
+ def __len__(self) -> int:
+ return len(self.strategies)
+
+ def __iter__(self):
+ return iter(self.strategies)
+
+ def __str__(self) -> str:
+ enabled_count = len([s for s in self.strategies if s.is_enabled()])
+ return (
+ f"StrategyChain(strategies={len(self.strategies)}, enabled={enabled_count})"
+ )
diff --git a/src/interactive_feedback_server/utils/rule_engine.py b/src/interactive_feedback_server/utils/rule_engine.py
new file mode 100644
index 0000000..a489151
--- /dev/null
+++ b/src/interactive_feedback_server/utils/rule_engine.py
@@ -0,0 +1,192 @@
+# src/interactive_feedback_server/utils/rule_engine.py
+"""
+规则引擎模块 - V3.3 架构改进版本
+Rule Engine Module - V3.3 Architecture Improvement Version
+
+V3.3 架构改进:集成可配置规则引擎,支持外部化配置
+V3.3 Architecture Improvement: Integrated configurable rule engine with externalized configuration
+
+V3.2 性能优化:集成缓存机制,显著提升处理速度
+V3.2 Performance Optimization: Integrated caching mechanism for significant speed improvement
+
+提供三层回退逻辑:
+1. AI提供的选项(第一层)
+2. 规则引擎生成的选项(第二层)
+3. 用户配置的后备选项(第三层)
+
+Three-layer fallback logic:
+1. AI-provided options (first layer)
+2. Rule engine generated options (second layer)
+3. User-configured fallback options (third layer)
+"""
+
+from typing import List, Dict, Any
+from .text_processor import fast_find_match
+
+# 规则引擎相关导入已移除 - V4.0 简化
+
+# 核心模式定义 - 精选高频场景
+CORE_PATTERNS = {
+ # 疑问场景 - 最高优先级
+ "question": {
+ "triggers": [
+ "?",
+ "?",
+ "是否",
+ "如何",
+ "怎么",
+ "什么",
+ "为什么",
+ "哪个",
+ "哪些",
+ ],
+ "options": ["是的", "不是", "需要更多信息"],
+ },
+ # 确认场景 - 高优先级
+ "confirmation": {
+ "triggers": ["确认", "同意", "继续", "下一步", "开始", "执行", "好的"],
+ "options": ["好的,继续", "我明白了", "暂停一下"],
+ },
+ # 选择场景 - 中优先级
+ "choice": {
+ "triggers": ["选择", "决定", "考虑", "建议", "推荐", "方案", "选项"],
+ "options": ["选择这个", "看看其他的", "让我想想"],
+ },
+ # 操作场景 - 中优先级
+ "action": {
+ "triggers": ["修改", "更改", "调整", "优化", "删除", "添加", "创建", "生成"],
+ "options": ["执行操作", "先预览", "取消操作"],
+ },
+}
+
+
+# V4.0 移除:extract_options_from_text 函数已删除
+# 规则引擎功能已完全移除,简化为AI选项+用户自定义选项的2级逻辑
+
+
+def is_valid_ai_options(ai_options) -> bool:
+ """
+ 严格验证AI选项的有效性 - V3.2边界控制
+ Strictly validate the validity of AI options - V3.2 boundary control
+
+ Args:
+ ai_options: AI提供的选项
+
+ Returns:
+ bool: 是否为有效的AI选项
+ """
+ # 检查是否为None
+ if ai_options is None:
+ return False
+
+ # 检查是否为空列表
+ if isinstance(ai_options, list) and len(ai_options) == 0:
+ return False
+
+ # 检查是否为非列表类型
+ if not isinstance(ai_options, list):
+ return False
+
+ # 检查列表中是否包含有效选项
+ valid_count = 0
+ for option in ai_options:
+ if isinstance(option, str) and option.strip():
+ valid_count += 1
+
+ # 至少要有一个有效选项
+ return valid_count > 0
+
+
+def resolve_final_options(
+ ai_options: List[str] = None, text: str = "", config: Dict[str, Any] = None
+) -> List[str]:
+ """
+ V4.0 简化的两层回退逻辑
+ V4.0 Simplified two-layer fallback logic
+
+ V4.0 简化改进:
+ - 移除规则引擎层,简化为AI选项 + 用户自定义选项
+ - 保持严格的边界控制
+ - 提高性能和可维护性
+
+ 严格的边界规则:
+ 1. 第一层:AI选项优先,有效时完全阻断后续层级
+ 2. 第二层:用户自定义选项,仅在AI选项无效时使用
+ 3. 每一层都有严格的有效性检查,确保边界清晰
+
+ Args:
+ ai_options: AI提供的预定义选项
+ text: 文本内容(保留参数以兼容现有调用)
+ config: 配置字典,包含用户自定义的后备选项
+
+ Returns:
+ List[str]: 最终的选项列表
+ """
+ # 导入配置管理器(避免循环导入)
+ from .config_manager import (
+ get_config,
+ safe_get_fallback_options,
+ get_custom_options_enabled,
+ )
+
+ if config is None:
+ config = get_config()
+
+ # 第一层:AI选项优先 - 严格边界检查
+ if is_valid_ai_options(ai_options):
+ # AI提供了有效选项,严格过滤并直接返回,完全阻断后续处理
+ valid_ai_options = [
+ option.strip()
+ for option in ai_options
+ if isinstance(option, str) and option.strip()
+ ]
+ if valid_ai_options: # 双重检查确保有效性
+ return valid_ai_options
+
+ # 第二层:用户自定义后备选项 - 可控制启用/禁用
+ custom_options_enabled = get_custom_options_enabled(config)
+ if custom_options_enabled:
+ try:
+ fallback_options = safe_get_fallback_options(config)
+ if fallback_options and len(fallback_options) > 0:
+ return fallback_options
+ except Exception:
+ # 后备选项获取失败,静默处理
+ pass
+
+ # V4.0 严格边界控制:如果用户禁用了所有层级,返回空选项
+ # 这样UI就不会显示任何选项,完全由用户手动输入
+ return []
+
+
+def get_options_summary(options: List[str]) -> str:
+ """
+ 获取选项的简要描述,用于调试和日志
+ Get brief description of options for debugging and logging
+
+ Args:
+ options: 选项列表
+
+ Returns:
+ str: 选项的简要描述
+ """
+ if not options:
+ return "无选项 (No options)"
+
+ if len(options) <= 3:
+ return f"选项: {', '.join(options)}"
+ else:
+ return f"选项: {', '.join(options[:3])}... (共{len(options)}个)"
+
+
+# V4.0 移除:规则引擎性能监控函数已删除
+
+
+# V4.0 移除:规则引擎管理函数已删除
+
+
+# V4.0 移除:规则引擎基准测试函数已删除
+
+
+# V4.0 移除:规则引擎测试函数已删除
+# 简化后的规则引擎只保留核心的2级逻辑:AI选项 → 用户自定义选项
diff --git a/src/interactive_feedback_server/utils/text_processor.py b/src/interactive_feedback_server/utils/text_processor.py
new file mode 100644
index 0000000..e9f7c75
--- /dev/null
+++ b/src/interactive_feedback_server/utils/text_processor.py
@@ -0,0 +1,305 @@
+# src/interactive_feedback_server/utils/text_processor.py
+"""
+文本处理优化器
+Text Processing Optimizer
+
+V3.2 第一阶段性能优化 - Day 3: 字符串处理优化
+V3.2 Phase 1 Performance Optimization - Day 3: String Processing Optimization
+
+提供高性能的文本预处理和关键词匹配功能,显著提升文本处理速度。
+Provides high-performance text preprocessing and keyword matching for significant speed improvement.
+
+特性 Features:
+- 预编译正则表达式 (Pre-compiled regex patterns)
+- 智能文本标准化 (Intelligent text normalization)
+- 高效关键词匹配 (Efficient keyword matching)
+- 文本处理缓存 (Text processing cache)
+"""
+
+import re
+from typing import Dict, List, Tuple, Optional
+from functools import lru_cache
+import unicodedata
+
+
+class TextProcessor:
+ """
+ 高性能文本处理器
+ High-Performance Text Processor
+
+ 优化文本预处理和关键词匹配性能。
+ Optimizes text preprocessing and keyword matching performance.
+ """
+
+ def __init__(self):
+ """初始化文本处理器"""
+ # 预编译正则表达式模式
+ self._whitespace_pattern = re.compile(r"\s+")
+ self._punctuation_pattern = re.compile(r"[^\w\s]")
+ self._number_pattern = re.compile(r"\d+")
+ self._english_pattern = re.compile(r"[a-zA-Z]+")
+ self._chinese_pattern = re.compile(r"[\u4e00-\u9fff]+")
+
+ # 简化标点符号处理,避免复杂映射
+ self._punctuation_map = None # 暂时不使用复杂映射
+
+ # 停用词集合(用于快速查找)
+ self._stop_words = {
+ "的",
+ "了",
+ "在",
+ "是",
+ "我",
+ "有",
+ "和",
+ "就",
+ "不",
+ "人",
+ "都",
+ "一",
+ "一个",
+ "上",
+ "也",
+ "很",
+ "到",
+ "说",
+ "要",
+ "去",
+ "你",
+ "会",
+ "着",
+ "没有",
+ "看",
+ "好",
+ "the",
+ "a",
+ "an",
+ "and",
+ "or",
+ "but",
+ "in",
+ "on",
+ "at",
+ "to",
+ "for",
+ "of",
+ "with",
+ "by",
+ "is",
+ "are",
+ "was",
+ "were",
+ "be",
+ "been",
+ "being",
+ "have",
+ "has",
+ }
+
+ # 文本长度阈值
+ self.MIN_TEXT_LENGTH = 2
+ self.MAX_TEXT_LENGTH = 1000
+
+ @lru_cache(maxsize=500)
+ def normalize_text(self, text: str) -> str:
+ """
+ 标准化文本(带缓存)
+ Normalize text with caching
+
+ Args:
+ text: 原始文本
+
+ Returns:
+ str: 标准化后的文本
+ """
+ if not text or not isinstance(text, str):
+ return ""
+
+ # 长度检查
+ if len(text) < self.MIN_TEXT_LENGTH:
+ return ""
+ if len(text) > self.MAX_TEXT_LENGTH:
+ text = text[: self.MAX_TEXT_LENGTH]
+
+ # 1. Unicode标准化
+ text = unicodedata.normalize("NFKC", text)
+
+ # 2. 转小写
+ text = text.lower()
+
+ # 3. 简化标点符号处理(移除或替换为空格)
+ text = self._punctuation_pattern.sub(" ", text)
+
+ # 4. 合并多个空白字符
+ text = self._whitespace_pattern.sub(" ", text)
+
+ # 5. 去除首尾空白
+ text = text.strip()
+
+ return text
+
+ @lru_cache(maxsize=300)
+ def extract_keywords(self, text: str) -> Tuple[str, ...]:
+ """
+ 提取关键词(带缓存)
+ Extract keywords with caching
+
+ Args:
+ text: 输入文本
+
+ Returns:
+ Tuple[str, ...]: 关键词元组(用于缓存)
+ """
+ normalized_text = self.normalize_text(text)
+ if not normalized_text:
+ return tuple()
+
+ # 分词
+ words = normalized_text.split()
+
+ # 过滤停用词和短词
+ keywords = []
+ for word in words:
+ if len(word) >= 2 and word not in self._stop_words and not word.isdigit():
+ keywords.append(word)
+
+ return tuple(keywords)
+
+ # V4.1 移除:get_text_features方法未在输入优化功能中使用
+
+
+class OptimizedMatcher:
+ """
+ 优化的关键词匹配器
+ Optimized Keyword Matcher
+
+ 使用高效算法进行关键词匹配。
+ Uses efficient algorithms for keyword matching.
+ """
+
+ def __init__(self, patterns: Dict[str, Dict[str, any]]):
+ """
+ 初始化匹配器
+ Initialize matcher
+
+ Args:
+ patterns: 模式字典,格式同CORE_PATTERNS
+ """
+ self.patterns = patterns
+ self._build_optimized_structures()
+
+ def _build_optimized_structures(self):
+ """构建优化的数据结构"""
+ # 按长度分组的触发词
+ self._triggers_by_length: Dict[int, List[Tuple[str, str, int]]] = {}
+
+ # 类别优先级
+ self._category_priority = {
+ "confirmation": 0,
+ "choice": 1,
+ "action": 2,
+ "question": 3,
+ }
+
+ # 构建触发词索引
+ for category, config in self.patterns.items():
+ priority = self._category_priority.get(category, 999)
+
+ for trigger in config["triggers"]:
+ trigger_len = len(trigger)
+
+ if trigger_len not in self._triggers_by_length:
+ self._triggers_by_length[trigger_len] = []
+
+ self._triggers_by_length[trigger_len].append(
+ (trigger, category, priority)
+ )
+
+ # 按长度降序排序(长词优先)
+ self._sorted_lengths = sorted(self._triggers_by_length.keys(), reverse=True)
+
+ # 对每个长度组内按优先级排序
+ for length in self._sorted_lengths:
+ self._triggers_by_length[length].sort(key=lambda x: x[2]) # 按优先级排序
+
+ @lru_cache(maxsize=200)
+ def find_best_match(self, text: str) -> Optional[Tuple[str, str, List[str]]]:
+ """
+ 查找最佳匹配(带缓存)
+ Find best match with caching
+
+ Args:
+ text: 输入文本
+
+ Returns:
+ Optional[Tuple[str, str, List[str]]]: (触发词, 类别, 选项列表) 或 None
+ """
+ if not text:
+ return None
+
+ text_lower = text.lower()
+
+ # 按长度优先级搜索
+ for length in self._sorted_lengths:
+ triggers = self._triggers_by_length[length]
+
+ for trigger, category, _ in triggers: # priority未使用
+ if trigger in text_lower:
+ options = self.patterns[category]["options"]
+ return (trigger, category, options)
+
+ return None
+
+ # V4.1 移除:find_all_matches方法未在输入优化功能中使用
+
+
+# 全局实例
+_text_processor = TextProcessor()
+_optimized_matcher = None # 延迟初始化
+
+
+def get_text_processor() -> TextProcessor:
+ """获取全局文本处理器实例"""
+ return _text_processor
+
+
+def get_optimized_matcher() -> OptimizedMatcher:
+ """获取全局优化匹配器实例"""
+ global _optimized_matcher
+
+ if _optimized_matcher is None:
+ # 延迟导入避免循环依赖
+ from .rule_engine import CORE_PATTERNS
+
+ _optimized_matcher = OptimizedMatcher(CORE_PATTERNS)
+
+ return _optimized_matcher
+
+
+def clear_text_processing_cache():
+ """清空文本处理缓存"""
+ _text_processor.normalize_text.cache_clear()
+ _text_processor.extract_keywords.cache_clear()
+
+ global _optimized_matcher
+ if _optimized_matcher:
+ _optimized_matcher.find_best_match.cache_clear()
+
+
+# V4.1 移除:get_text_processing_stats函数未在输入优化功能中使用
+
+
+# 便捷函数
+def fast_normalize_text(text: str) -> str:
+ """快速文本标准化"""
+ return _text_processor.normalize_text(text)
+
+
+def fast_extract_keywords(text: str) -> Tuple[str, ...]:
+ """快速关键词提取"""
+ return _text_processor.extract_keywords(text)
+
+
+def fast_find_match(text: str) -> Optional[Tuple[str, str, List[str]]]:
+ """快速匹配查找"""
+ return get_optimized_matcher().find_best_match(text)
diff --git a/uv.lock b/uv.lock
deleted file mode 100644
index f28d2b1..0000000
--- a/uv.lock
+++ /dev/null
@@ -1,522 +0,0 @@
-version = 1
-revision = 2
-requires-python = ">=3.11"
-
-[[package]]
-name = "annotated-types"
-version = "0.7.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
-]
-
-[[package]]
-name = "anyio"
-version = "4.9.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "idna" },
- { name = "sniffio" },
- { name = "typing-extensions", marker = "python_full_version < '3.13'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
-]
-
-[[package]]
-name = "certifi"
-version = "2025.1.31"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" },
-]
-
-[[package]]
-name = "click"
-version = "8.1.8"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "colorama", marker = "sys_platform == 'win32'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
-]
-
-[[package]]
-name = "colorama"
-version = "0.4.6"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
-]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.2.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" },
-]
-
-[[package]]
-name = "fastmcp"
-version = "2.3.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "exceptiongroup" },
- { name = "httpx" },
- { name = "mcp" },
- { name = "openapi-pydantic" },
- { name = "python-dotenv" },
- { name = "rich" },
- { name = "typer" },
- { name = "websockets" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/2f/e7/a37b7bf39ee9bcc01b51319b94759981be0fde84526e5c7c479d2abbbefd/fastmcp-2.3.0.tar.gz", hash = "sha256:28c8799d1c28c2d10cca91dd2076c33ee459b69484e2e39d34de11f9a88b628f", size = 978654, upload-time = "2025-05-08T20:29:18.112Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/72/22/494b4b037c87af874f932cc6810221738c0aaad044d58537e85d91f9a0ec/fastmcp-2.3.0-py3-none-any.whl", hash = "sha256:1634e88111adadd790e1d39a5f83248ef814643ac5643f9ad0a1986884e2bdad", size = 90411, upload-time = "2025-05-08T20:29:16.645Z" },
-]
-
-[[package]]
-name = "h11"
-version = "0.14.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" },
-]
-
-[[package]]
-name = "httpcore"
-version = "1.0.7"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "certifi" },
- { name = "h11" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196, upload-time = "2024-11-15T12:30:47.531Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload-time = "2024-11-15T12:30:45.782Z" },
-]
-
-[[package]]
-name = "httpx"
-version = "0.28.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "anyio" },
- { name = "certifi" },
- { name = "httpcore" },
- { name = "idna" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
-]
-
-[[package]]
-name = "httpx-sse"
-version = "0.4.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" },
-]
-
-[[package]]
-name = "idna"
-version = "3.10"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
-]
-
-[[package]]
-name = "interactive-feedback-mcp"
-version = "0.1.0"
-source = { virtual = "." }
-dependencies = [
- { name = "fastmcp" },
- { name = "psutil" },
- { name = "pyside6" },
-]
-
-[package.metadata]
-requires-dist = [
- { name = "fastmcp", specifier = ">=2.0.0" },
- { name = "psutil", specifier = ">=7.0.0" },
- { name = "pyside6", specifier = ">=6.8.2.1" },
-]
-
-[[package]]
-name = "markdown-it-py"
-version = "3.0.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "mdurl" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
-]
-
-[[package]]
-name = "mcp"
-version = "1.8.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "anyio" },
- { name = "httpx" },
- { name = "httpx-sse" },
- { name = "pydantic" },
- { name = "pydantic-settings" },
- { name = "python-multipart" },
- { name = "sse-starlette" },
- { name = "starlette" },
- { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/ff/97/0a3e08559557b0ac5799f9fb535fbe5a4e4dcdd66ce9d32e7a74b4d0534d/mcp-1.8.0.tar.gz", hash = "sha256:263dfb700540b726c093f0c3e043f66aded0730d0b51f04eb0a3eb90055fe49b", size = 264641, upload-time = "2025-05-08T20:09:06.255Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b2/b2/4ac3bd17b1fdd65658f18de4eb0c703517ee0b483dc5f56467802a9197e0/mcp-1.8.0-py3-none-any.whl", hash = "sha256:889d9d3b4f12b7da59e7a3933a0acadae1fce498bfcd220defb590aa291a1334", size = 119544, upload-time = "2025-05-08T20:09:04.458Z" },
-]
-
-[[package]]
-name = "mdurl"
-version = "0.1.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
-]
-
-[[package]]
-name = "openapi-pydantic"
-version = "0.5.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pydantic" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" },
-]
-
-[[package]]
-name = "psutil"
-version = "7.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" },
- { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" },
- { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" },
- { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" },
- { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" },
- { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" },
- { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
-]
-
-[[package]]
-name = "pydantic"
-version = "2.10.6"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "annotated-types" },
- { name = "pydantic-core" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" },
-]
-
-[[package]]
-name = "pydantic-core"
-version = "2.27.2"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" },
- { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" },
- { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" },
- { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" },
- { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" },
- { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" },
- { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" },
- { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" },
- { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" },
- { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" },
- { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" },
- { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" },
- { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" },
- { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" },
- { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" },
- { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" },
- { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" },
- { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" },
- { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" },
- { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" },
- { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" },
- { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" },
- { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" },
- { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" },
- { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" },
- { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" },
- { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" },
- { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" },
- { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" },
- { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" },
- { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" },
- { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" },
- { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" },
- { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" },
- { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" },
- { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" },
- { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" },
- { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" },
- { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" },
- { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" },
- { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" },
- { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" },
-]
-
-[[package]]
-name = "pydantic-settings"
-version = "2.8.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pydantic" },
- { name = "python-dotenv" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550, upload-time = "2025-02-27T10:10:32.338Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" },
-]
-
-[[package]]
-name = "pygments"
-version = "2.19.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
-]
-
-[[package]]
-name = "pyside6"
-version = "6.8.2.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pyside6-addons" },
- { name = "pyside6-essentials" },
- { name = "shiboken6" },
-]
-wheels = [
- { url = "https://files.pythonhosted.org/packages/14/0f/bdb12758448b52497dba7a3bbfb5855dfb29129c64ddbda4da56c4b11f6c/PySide6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:3fcb551729f235475b2abe7d919027de54a65d850e744f60716f890202273720", size = 550254, upload-time = "2025-02-06T13:56:07.585Z" },
- { url = "https://files.pythonhosted.org/packages/fa/00/0b232a25eeb8671202d7a7ec92893bd25b965debfd1d5d7aad637b067efe/PySide6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:23d2a1a77b25459a049c4276b4e0bbfb375b73d3921061b1a16bcfa64e1fe517", size = 550489, upload-time = "2025-02-06T13:56:09.913Z" },
- { url = "https://files.pythonhosted.org/packages/8b/8a/9eb78cf71233399236c257cf85770ca4673ed0b9b959895856285157f643/PySide6-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bfefa80a93db06dc64c0e7beef0377c9b8ca51e007cfc34575defe065af893b6", size = 550491, upload-time = "2025-02-06T13:56:12.51Z" },
- { url = "https://files.pythonhosted.org/packages/fb/3d/3e626e1953408cb8977a050ce54b1f1adff9a4c06bb519f6d56ebaf9310c/PySide6-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:92361e41727910e3560ea5ba494fabecc76cd20892c9fcb2ced07619081c4e65", size = 556167, upload-time = "2025-02-06T13:56:15.394Z" },
-]
-
-[[package]]
-name = "pyside6-addons"
-version = "6.8.2.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pyside6-essentials" },
- { name = "shiboken6" },
-]
-wheels = [
- { url = "https://files.pythonhosted.org/packages/6e/79/a868ffac6eb446afdd25312b61872d0d11173032d50320d48b5277b68ccf/PySide6_Addons-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:5558816018042fecd0d782111ced529585a23ea9a010b518f8495764f578a01f", size = 302704501, upload-time = "2025-02-06T13:50:40.242Z" },
- { url = "https://files.pythonhosted.org/packages/95/3a/93e0028805c50ceff8b8ae0f274d502805b8a864129b83d705ab12d48f78/PySide6_Addons-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f3d85e676851ada8238bc76ebfacbee738fc0b35b3bc15c9765dd107b8ee6ec4", size = 160641392, upload-time = "2025-02-06T13:51:32.153Z" },
- { url = "https://files.pythonhosted.org/packages/84/5c/e822e4ef6c2140b273cb0f8531d7e200c8771bd61832decc524fc318c335/PySide6_Addons-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:d904179f16deeca4ba440b4ef78e8d54df2b994b46784ad9d53b741082f3b2a7", size = 156398179, upload-time = "2025-02-06T13:51:57.622Z" },
- { url = "https://files.pythonhosted.org/packages/0a/f8/98f85194f85a1fcff44ad98cd80cf6e856f7edee9e744fba81dec48b0ae9/PySide6_Addons-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:c761cc45022aa79d8419e671e7fb34a4a3e5b3826f1e68fcb819bd6e3a387fbb", size = 127973648, upload-time = "2025-02-06T13:52:22.998Z" },
-]
-
-[[package]]
-name = "pyside6-essentials"
-version = "6.8.2.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "shiboken6" },
-]
-wheels = [
- { url = "https://files.pythonhosted.org/packages/01/bb/0127a53530cec0f9e7268e2fe235322b7b6e592caeb36c558b64da6ec52c/PySide6_Essentials-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:ae5cc48f7e9a08e73e3ec2387ce245c8150e620b8d5a87548ebd4b8e3aeae49b", size = 134909713, upload-time = "2025-02-06T13:53:07.533Z" },
- { url = "https://files.pythonhosted.org/packages/d2/f9/aa4ff511ff1f3dd177f7e8f5a635e03fe578fa2045c8d6be4577e7db3b28/PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5ab31e5395a4724102edd6e8ff980fa3f7cde2aa79050763a1dcc30bb914195a", size = 95331575, upload-time = "2025-02-06T13:53:26.04Z" },
- { url = "https://files.pythonhosted.org/packages/fd/69/595002d860ee58431fe7add081d6f54fff94ae9680f2eb8cd355c1649bb6/PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:7aed46f91d44399b4c713cf7387f5fb6f0114413fbcdbde493a528fb8e19f6ed", size = 93200219, upload-time = "2025-02-06T13:53:41.404Z" },
- { url = "https://files.pythonhosted.org/packages/5b/54/28a8b03f327e2c1d27d4a1ccf1a44997afc73c00ad07125d889640367194/PySide6_Essentials-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:18de224f09108998d194e60f2fb8a1e86367dd525dd8a6192598e80e6ada649e", size = 72502927, upload-time = "2025-02-06T13:53:53.124Z" },
-]
-
-[[package]]
-name = "python-dotenv"
-version = "1.1.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
-]
-
-[[package]]
-name = "python-multipart"
-version = "0.0.20"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
-]
-
-[[package]]
-name = "rich"
-version = "13.9.4"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "markdown-it-py" },
- { name = "pygments" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" },
-]
-
-[[package]]
-name = "shellingham"
-version = "1.5.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
-]
-
-[[package]]
-name = "shiboken6"
-version = "6.8.2.1"
-source = { registry = "https://pypi.org/simple" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d8/8f/71ccc3642edb59efaca35d4ba974248b1d7847f5e4d87d3ea323e73b2cab/shiboken6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:d3dedeb3732ecfc920c9f97da769c0022a1c3bda99346a9eba56fbf093deaa75", size = 401266, upload-time = "2025-02-06T13:55:54.499Z" },
- { url = "https://files.pythonhosted.org/packages/7b/ff/ab4f287b9573e50b5a47c10e2af8feb5abecc3c7431bd5deec135efc969e/shiboken6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c83e90056f13d0872cc4d2b7bf60b6d6e3b1b172f1f91910c0ba5b641af01758", size = 204273, upload-time = "2025-02-06T13:55:56.926Z" },
- { url = "https://files.pythonhosted.org/packages/a6/b0/4fb102eb5260ee06d379769f3c4f0b82ef397c15f1cbbbbb3f6dceb86d5d/shiboken6-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8592401423acc693f51dbbfae5e7493cc3ed6738be79daaf90afa07f4da5bb25", size = 200909, upload-time = "2025-02-06T13:55:58.317Z" },
- { url = "https://files.pythonhosted.org/packages/ae/88/b56bdb38a11066e4eecd1da6be4205bb406398b733b392b11c5aaf9547f7/shiboken6-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:1b751d47b759762b7ca31bad278d52eca4105d3028880d93979261ebbfba810c", size = 1150270, upload-time = "2025-02-06T13:56:00.094Z" },
-]
-
-[[package]]
-name = "sniffio"
-version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
-]
-
-[[package]]
-name = "sse-starlette"
-version = "2.2.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "anyio" },
- { name = "starlette" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376, upload-time = "2024-12-25T09:09:30.616Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120, upload-time = "2024-12-25T09:09:26.761Z" },
-]
-
-[[package]]
-name = "starlette"
-version = "0.46.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "anyio" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102, upload-time = "2025-03-08T10:55:34.504Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995, upload-time = "2025-03-08T10:55:32.662Z" },
-]
-
-[[package]]
-name = "typer"
-version = "0.15.2"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "click" },
- { name = "rich" },
- { name = "shellingham" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711, upload-time = "2025-02-27T19:17:34.807Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061, upload-time = "2025-02-27T19:17:32.111Z" },
-]
-
-[[package]]
-name = "typing-extensions"
-version = "4.12.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" },
-]
-
-[[package]]
-name = "uvicorn"
-version = "0.34.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "click" },
- { name = "h11" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568, upload-time = "2024-12-15T13:33:30.42Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload-time = "2024-12-15T13:33:27.467Z" },
-]
-
-[[package]]
-name = "websockets"
-version = "15.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" },
- { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" },
- { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" },
- { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" },
- { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" },
- { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" },
- { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" },
- { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" },
- { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" },
- { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" },
- { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" },
- { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
- { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
- { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
- { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
- { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
- { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
- { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
- { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
- { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
- { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
- { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
- { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
- { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
- { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
- { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
- { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
- { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
- { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
- { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
- { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
- { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
- { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
- { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
-]
diff --git "a/\345\212\237\350\203\275\350\257\264\346\230\216.md" "b/\345\212\237\350\203\275\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..bd716c5
--- /dev/null
+++ "b/\345\212\237\350\203\275\350\257\264\346\230\216.md"
@@ -0,0 +1,215 @@
+# interactive-feedback-mcp 功能说明
+
+本文档详细介绍 `interactive-feedback-mcp` 服务的各项功能,旨在帮助用户全面了解其用途、交互方式以及与 AI 助手的协作流程。
+
+## 快速开始
+
+### 安装方式
+
+**推荐方式(开发模式):**
+```bash
+# 克隆仓库
+git clone https://github.com/pawaovo/interactive-feedback-mcp.git
+cd interactive-feedback-mcp
+
+# 安装依赖
+uv pip install -e .
+
+# 或使用现代 uv 语法
+# uv sync
+```
+
+**备选方式(uvx):**
+```bash
+# 直接运行,无需安装
+uvx interactive-feedback@latest
+
+# 如果首次安装失败,可以预安装:
+uv tool install interactive-feedback@latest
+```
+
+**或使用pip:**
+```bash
+pip install interactive-feedback
+```
+
+**当前版本:** v2.5.10 - 文档重大更新,推荐开发模式安装;修复UI控件选中状态视觉效果
+
+**PyPI项目页面:** https://pypi.org/project/interactive-feedback/
+
+### MCP配置示例
+
+**推荐配置(开发模式):**
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/path/to/interactive-feedback-mcp",
+ "run",
+ "interactive-feedback"
+ ],
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+**备选配置(uvx):**
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uvx",
+ "args": [
+ "interactive-feedback@latest"
+ ],
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+**请将 `/path/to/interactive-feedback-mcp` 替换为您实际的项目路径。**
+
+详细安装和配置说明请参阅 [安装与配置指南.md](./安装与配置指南.md)。
+
+## 1. 项目简介
+
+`interactive-feedback-mcp` 是一个模型上下文协议 (MCP) 服务,它通过在 AI 助手(如 Cursor)需要用户输入、澄清或确认时,弹出一个功能丰富的图形用户界面 (GUI) 反馈窗口。此服务旨在提升人与 AI 之间协作的效率、准确性和灵活性,支持文本、图片以及文件引用等多种反馈形式。
+
+## 2. 核心功能
+
+### 2.1. 交互式反馈窗口
+
+* **触发方式**:
+ * AI 助手通过调用本 MCP 服务提供的 `interactive_feedback` 工具时,会自动弹出反馈窗口。
+ * 用户也可以主动告知 AI 助手:"请用 `interactive_feedback mcp` 工具与我对话"来手动触发。
+* **文本输入**:用户可以在主输入框中输入纯文本反馈。支持通过按 `Enter`键发送反馈,按 `Shift+Enter` 组合键进行换行。
+* **预定义选项**:如果 AI 助手在调用时提供了 `predefined_options` 参数,这些选项会以带文本描述的复选框形式显示。用户可以直接勾选一个或多个选项,选中的选项文本会自动整合到最终发送的反馈内容中。
+
+### 2.2. 图片处理与反馈
+
+* **图片粘贴**:用户可以直接在反馈输入框中使用 `Ctrl+V` (或 macOS 上的 `Cmd+V`) 粘贴剪贴板中的单张或多张图片。
+* **图片拖拽**:支持从本地文件系统直接拖拽图片文件到文本输入框中进行添加。
+* **图片预览与管理**:
+ * 添加的图片会在文本输入框下方以缩略图形式显示。
+ * 鼠标悬停在缩略图上会显示更大尺寸的图片预览及图片尺寸信息。
+ * 点击缩略图可以直接删除该图片。
+* **图片处理机制**:为了优化传输和 AI 处理,图片在发送前会进行处理:
+ * 尺寸调整:较大的图片会被缩放到预设的最大宽度和高度(例如 512x512 像素),同时保持宽高比。
+ * 格式与压缩:图片统一转换为 JPEG 格式,并可能根据需要调整压缩质量以满足大小限制(例如 1MB)。
+* **发送方式**:图片数据会经过 Base64 编码后,与文本内容一起作为结构化数据返回给 AI 助手。
+
+### 2.3. 文件处理
+
+* **文件拖拽**:将本地文件拖拽到文本输入框,生成蓝色加粗的文件引用(如 `@文件名.txt`)。
+* **文件选择**:点击"选择文件"按钮打开文件选择对话框,支持多文件选择。
+* **智能处理**:自动识别图片文件和普通文件,分别进行相应的处理和显示。
+* **智能光标定位**:文件添加后光标自动定位到合适位置,便于继续输入。
+
+### 2.4. 常用语管理
+
+* **预设管理**:预设和管理常用的反馈短语,支持添加、编辑、删除和排序。
+* **快速预览**:鼠标悬停在"常用语"按钮上显示预览窗口,支持滚动查看所有常用语。
+* **快速插入**:在预览窗口中点击常用语直接插入到输入框,或通过管理对话框双击插入。
+* **主题适配**:预览窗口和管理界面支持深色/浅色主题切换。
+
+
+
+### 2.5. 界面布局
+
+* **双布局模式**:支持垂直布局(上下分布)和水平布局(左右分布)。
+* **可拖拽分割器**:支持拖拽分割器调整各区域大小,双击重置为默认比例。
+* **实时切换**:在设置页面可以实时切换布局模式,状态自动保存。
+
+### 2.6. 文本优化功能
+
+* **一键优化**:将口语化输入转换为结构化、逻辑清晰的指令。
+* **自定义增强**:支持用户自定义增强指令,按特定要求处理文本内容。
+* **多AI提供商**:支持OpenAI、Google Gemini、DeepSeek、火山引擎等多个AI提供商。
+* **API密钥管理**:通过设置页面统一管理所有提供商的API密钥。
+* **优化体验**:支持加载动画、撤销功能(Ctrl+Z)、自动光标定位等。
+
+### 2.7. 其他功能
+
+* **音频提示**:窗口弹出时自动播放提示音,支持自定义音频文件。
+* **窗口截图**:支持矩形选择截图,截图完成后自动添加到输入内容中。
+* **显示模式**:支持简单模式(显示简洁问题)和完整模式(显示完整回复),可在设置页面实时切换。
+* **窗口控制**:支持窗口固定、自动最小化、UI状态持久化等功能。
+
+
+
+## 3. 提供的 MCP 工具
+
+本服务通过 MCP 向 AI 助手公开以下核心工具:
+
+### `interactive_feedback`
+
+* **功能**:向用户发起交互式会话,显示提示信息,提供可选选项,并收集用户的文本、图片和文件引用反馈。
+* **参数**:
+ * `message` (str, 可选): 简单模式下显示的简洁问题或提示
+ * `full_response` (str, 可选): 完整模式下显示的AI原始完整回复内容
+ * `predefined_options` (List[str], 可选): 一个字符串列表,每个字符串代表一个用户可以选择的预定义选项。如果提供,这些选项会显示为复选框。
+* **显示模式说明**:
+ * **简单模式**:优先使用 `message` 参数内容,显示简洁的问题或提示
+ * **完整模式**:优先使用 `full_response` 参数内容,显示完整的AI回复
+ * **智能回退**:如果主要参数为空,自动回退到备用参数,避免调用失败
+ * **实时模式检测**:每次调用都读取最新的用户模式配置,支持动态切换
+* **返回给AI助手的数据格式**:
+ 该工具会返回一个包含结构化反馈内容的元组 (Tuple)。元组中的每个元素可以是字符串 (文本反馈或文件引用信息) 或 `fastmcp.Image` 对象 (图片反馈)。
+ 具体来说,从UI收集到的数据会转换成以下 `content` 项列表,并由 MCP 服务器进一步处理成 FastMCP兼容的元组:
+ ```json
+ // UI返回给MCP服务器的原始JSON结构示例
+ {
+ "content": [
+ {"type": "text", "text": "用户的文本反馈..."},
+ {"type": "image", "data": "base64_encoded_image_data", "mimeType": "image/jpeg"},
+ {"type": "file_reference", "display_name": "@example.txt", "path": "/path/to/local/example.txt"}
+ // ... 可能有更多项
+ ]
+ }
+ ```
+ * **文本内容** (`type: "text"`):包含用户输入的文本和/或选中的预定义选项组合文本。
+ * **图片内容** (`type: "image"`):包含 Base64 编码后的图片数据和图片的 MIME 类型 (如 `image/jpeg`)。这些在 MCP 服务器中会被转换为 `fastmcp.Image` 对象。
+ * **文件引用** (`type: "file_reference"`):包含用户拖拽的文件的显示名 (如 `@filename.txt`) 和其在用户本地的完整路径。这些信息通常会作为文本字符串传递给AI助手。
+
+ **注意**:即使没有任何用户输入(例如用户直接关闭反馈窗口),工具也会返回一个表示"无反馈"的特定消息,如 `("[User provided no feedback]",)`。
+
+### `optimize_user_input`
+
+* **功能**:使用配置的LLM API来优化或增强用户输入的文本,将口语化、可能存在歧义的输入转化为更结构化、更清晰、更便于AI模型理解的文本。
+* **参数**:
+ * `original_text` (str): **必须参数**。用户的原始输入文本
+ * `mode` (str): **必须参数**。优化模式:
+ * `'optimize'`: 一键优化,使用预设的通用优化指令
+ * `'reinforce'`: 提示词强化,使用用户自定义的强化指令
+ * `reinforcement_prompt` (str, 可选): 在 'reinforce' 模式下用户的自定义指令
+* **支持的AI提供商**:
+ * **OpenAI**: GPT-4o-mini 等模型,提供高质量的文本优化
+ * **Google Gemini**: Gemini-2.0-flash 等模型,快速响应和处理
+ * **DeepSeek**: DeepSeek-chat 等模型,成本效益优化
+ * **火山引擎**: DeepSeek-v3 等模型,国内访问优化
+* **配置要求**:
+ * 需要在UI设置页面配置相应提供商的API密钥
+ * 支持自定义优化和增强提示词
+ * 提供完善的错误处理和重试机制
+* **返回**:优化后的文本内容或详细的错误信息
+
+## 4. 界面与体验特性
+
+* **深色主题UI**:界面采用深色主题,提供舒适的视觉体验。
+* **快捷键支持**:支持Enter提交、Shift+Enter换行、Ctrl+V粘贴等快捷键操作。
+* **智能交互**:输入框智能提示、hover预览、流畅的鼠标交互体验。
+* **响应式布局**:界面支持不同布局模式,能根据窗口大小自动调整元素排列。
+* **富文本支持**:支持带颜色的文件引用显示和文本格式化。
\ No newline at end of file
diff --git "a/\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256\346\214\207\345\215\227.md" "b/\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256\346\214\207\345\215\227.md"
new file mode 100644
index 0000000..43f6ab1
--- /dev/null
+++ "b/\345\256\211\350\243\205\344\270\216\351\205\215\347\275\256\346\214\207\345\215\227.md"
@@ -0,0 +1,672 @@
+# interactive-feedback-mcp 安装与配置指南
+
+欢迎!本文档旨在指导您完成 `interactive-feedback-mcp` 服务的安装、配置以及在 AI 助手(如 Cursor)中的设置步骤。
+
+## 目录
+- [interactive-feedback-mcp 安装与配置指南](#interactive-feedback-mcp-安装与配置指南)
+ - [目录](#目录)
+ - [开发安装(推荐)](#开发安装推荐)
+ - [开发安装步骤](#开发安装步骤)
+ - [快速安装(备选)](#快速安装备选)
+ - [方式一:使用uvx](#方式一使用uvx)
+ - [方式二:使用pip](#方式二使用pip)
+ - [环境准备](#环境准备)
+ - [Python](#python)
+ - [uv (Python 包安装工具)](#uv-python-包安装工具)
+ - [下载项目](#下载项目)
+ - [安装依赖](#安装依赖)
+ - [配置 MCP 服务](#配置-mcp-服务)
+ - [找到 `mcp_servers.json`](#找到-mcp_serversjson)
+ - [配置方式一:开发模式(推荐)](#配置方式一开发模式推荐)
+ - [推荐配置方式:开发模式 + UI 设置](#推荐配置方式开发模式--ui-设置)
+ - [配置方式二:uvx(备选)](#配置方式二uvx备选)
+ - [配置方式三:pip安装(备选)](#配置方式三pip安装备选)
+- [开发模式环境变量配置已移除,请使用 UI 设置页面管理 API key](#开发模式环境变量配置已移除请使用-ui-设置页面管理-api-key)
+ - [配置文件管理](#配置文件管理)
+ - [配置文件概述](#配置文件概述)
+ - [1. `config.json` - 本地配置文件](#1-configjson---本地配置文件)
+ - [2. `config.template.json` - 配置模板文件](#2-configtemplatejson---配置模板文件)
+ - [配置管理方式](#配置管理方式)
+ - [当前推荐的配置方式](#当前推荐的配置方式)
+ - [✅ 推荐方式:UI 设置页面](#-推荐方式ui-设置页面)
+ - [❌ 不再支持的方式](#-不再支持的方式)
+ - [配置优先级](#配置优先级)
+ - [用户使用指南](#用户使用指南)
+ - [源代码用户](#源代码用户)
+ - [uvx 用户](#uvx-用户)
+ - [安全性说明](#安全性说明)
+ - [为什么移除配置文件中的 API key 支持?](#为什么移除配置文件中的-api-key-支持)
+ - [安全最佳实践](#安全最佳实践)
+ - [配置 AI 助手规则](#配置-ai-助手规则)
+ - [故障排除](#故障排除)
+ - [uvx安装故障排除](#uvx安装故障排除)
+ - [MCP配置问题](#mcp配置问题)
+ - [AI助手特定配置](#ai助手特定配置)
+ - [uv安装用户常见问题解决](#uv安装用户常见问题解决)
+ - [问题1:看不到预定义选项,只显示"继续"、"取消"等通用选项](#问题1看不到预定义选项只显示继续取消等通用选项)
+ - [问题2:音频提示音不工作或设置页面显示音频文件缺失](#问题2音频提示音不工作或设置页面显示音频文件缺失)
+ - [问题3:配置文件位置不明确](#问题3配置文件位置不明确)
+ - [注意事项](#注意事项)
+
+## 开发安装(推荐)
+
+**推荐使用开发模式安装,以获得最佳的稳定性和功能完整性。**
+
+开发模式安装提供:
+- ✅ 完整的功能支持和最佳稳定性
+- ✅ 实时的代码更新和bug修复
+- ✅ 完整的资源文件和配置支持
+- ✅ 更好的调试和问题排查能力
+- ✅ 避免PyPI安装可能遇到的资源文件缺失问题
+
+**当前版本:** v2.5.10 - 文档重大更新,推荐开发模式安装;修复UI控件选中状态视觉效果
+
+### 开发安装步骤
+
+如果您需要修改代码、参与开发,或希望获得最稳定的使用体验,请使用开发安装:
+
+## 快速安装(备选)
+
+### 方式一:使用uvx
+
+**无需安装,直接运行:**
+```bash
+uvx interactive-feedback@latest
+```
+
+**如果首次安装失败(通常由于PySide6等大包下载超时),可以预安装:**
+```bash
+uv tool install interactive-feedback@latest
+```
+
+### 方式二:使用pip
+
+**安装到系统或虚拟环境:**
+```bash
+pip install interactive-feedback
+```
+
+**PyPI项目页面:** https://pypi.org/project/interactive-feedback/
+
+**注意:** PyPI安装可能存在资源文件缺失或配置问题,推荐使用开发模式安装。
+
+## 环境准备
+
+### Python
+确保您的系统已安装 Python 3.11 或更高版本。您可以从 [Python 官方网站](https://www.python.org/downloads/) 下载并安装。
+
+安装完成后,可以在终端或命令提示符中运行以下命令来验证 Python 版本:
+```bash
+python --version
+# 或者
+python3 --version
+```
+项目的 `pyproject.toml` 文件也指定了 `requires-python = ">=3.11"`。
+
+### uv (Python 包安装工具)
+本项目推荐使用 `uv` 进行包管理,它是一个非常快速的 Python 包安装和解析工具。
+
+* **Windows**:
+ ```bash
+ pip install uv
+ ```
+ 如果您尚未安装 `pip`,请先安装 Python,`pip` 通常会随之安装。
+
+* **Linux/macOS**:
+ ```bash
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ ```
+ 或者参考 [`uv` 官方文档](https://github.com/astral-sh/uv) 获取其他安装方式。
+
+安装完成后,可以通过运行 `uv --version` 来验证 `uv` 是否安装成功。
+
+## 下载项目
+您可以使用 Git 克隆本项目的代码仓库到您的本地计算机。打开终端或命令提示符,然后运行以下命令:
+```bash
+git clone https://github.com/pawaovo/interactive-feedback-mcp.git
+```
+这将在您当前的目录下创建一个名为 `interactive-feedback-mcp` 的文件夹,其中包含所有项目文件。
+
+如果您不熟悉 Git,也可以直接从 GitHub 仓库页面下载 ZIP 压缩包并解压。
+
+## 安装依赖
+项目运行所需的 Python 包在 `pyproject.toml` 文件中定义。项目已迁移到现代 Python 包管理,不再使用 `requirements.txt` 文件。
+
+1. 首先,请确保您已经按照上述步骤下载了项目,并在终端或命令提示符中进入项目的主目录:
+ ```bash
+ cd path/to/interactive-feedback-mcp
+ ```
+ 请将 `path/to/interactive-feedback-mcp` 替换为您实际的项目路径,如 `C:/Users/YourName/Projects/interactive-feedback-mcp` 。
+
+2. 然后,使用 `uv` 安装项目依赖(开发模式安装):
+ ```bash
+ uv pip install -e .
+ ```
+ 此命令会读取 `pyproject.toml` 文件,并自动下载和安装所有必要的库。`-e` 参数表示可编辑安装,这样对源代码的修改会立即生效。
+
+ **或者使用现代 uv 语法:**
+ ```bash
+ uv sync
+ ```
+ 此命令使用 `uv.lock` 文件进行精确的依赖安装,确保版本一致性。
+
+ `pyproject.toml` 中包含的主要依赖项及其用途如下:
+ * `PySide6-Essentials>=6.8.2.1`: 用于创建图形用户界面 (GUI)。
+ * `pyperclip>=1.8.2`: 用于跨平台的剪贴板操作(复制和粘贴)。
+ * `Pillow>=9.0.0`: Python 图像处理库,用于处理和显示粘贴的图片。
+ * `fastmcp>=2.0.0`: 模型上下文协议 (MCP) 的核心库。
+ * `psutil>=7.0.0`: 用于访问系统进程和系统利用率信息。
+ * `openai>=1.0.0`: 用于文本优化功能的AI提供商支持。
+ * `pywin32>=228; sys_platform == "win32"`: 提供了访问 Windows 系统特定API的功能,仅在 Windows 系统上安装和使用。
+
+ `uv` 会自动处理特定平台的依赖项。
+
+## 配置 MCP 服务
+为了让您的 AI 助手(如 Cursor)能够使用此 `interactive-feedback-mcp` 服务,您需要配置其 MCP 服务器设置。
+
+### 找到 `mcp_servers.json`
+此文件通常位于 AI 助手的用户配置目录中。对于 Cursor,它通常是:
+* Windows: `%APPDATA%\Cursor\.cursor-ai\mcp_servers.json` 或 `~/.cursor-ai/mcp_servers.json`
+* macOS: `~/.cursor-ai/mcp_servers.json`
+* Linux: `~/.config/cursor/.cursor-ai/mcp_servers.json` 或 `~/.cursor-ai/mcp_servers.json`
+
+如果文件不存在,您可以创建一个。
+
+### 配置方式一:开发模式(推荐)
+
+**最稳定的配置方式,使用现代 uv 包管理:**
+
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/path/to/interactive-feedback-mcp",
+ "run",
+ "interactive-feedback"
+ ],
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+**请将 `/path/to/interactive-feedback-mcp` 替换为您实际的项目路径。**
+
+**备选配置(如果已通过 `uv pip install -e .` 安装):**
+
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "interactive-feedback",
+ "cwd": "/path/to/interactive-feedback-mcp",
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+### 推荐配置方式:开发模式 + UI 设置
+
+**MCP JSON 中配置开发模式,API key 通过 UI 设置页面管理:**
+
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uv",
+ "args": [
+ "--directory",
+ "/path/to/interactive-feedback-mcp",
+ "run",
+ "interactive-feedback"
+ ],
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+### 配置方式二:uvx(备选)
+
+**如果您选择使用uvx安装:**
+
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uvx",
+ "args": [
+ "interactive-feedback@latest"
+ ],
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+**开发模式优势:**
+- ✅ **最佳稳定性**:完整的功能支持和资源文件
+- ✅ **实时更新**:可以获得最新的代码修复
+- ✅ **完整功能**:避免PyPI安装可能遇到的问题
+- ✅ **灵活配置**:API key 通过 UI 界面管理
+- ✅ **多提供商**:支持多个 AI 提供商配置和切换
+- ✅ **用户友好**:直观的图形界面配置
+
+**使用步骤:**
+1. 克隆仓库并安装依赖
+2. 在 MCP JSON 中添加开发模式配置
+3. 重启 AI 助手
+4. 在 UI 设置页面中配置 API key
+5. 开始使用所有功能
+
+### 配置方式三:pip安装(备选)
+
+**如果您使用pip安装了包:**
+
+```json
+{
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "interactive-feedback",
+ "timeout": 600,
+ "autoApprove": [
+ "interactive_feedback",
+ "optimize_user_input"
+ ]
+ }
+ }
+}
+```
+
+# 开发模式环境变量配置已移除,请使用 UI 设置页面管理 API key
+
+**重要说明:**
+* **路径替换**: 将配置中的路径替换为您在本地计算机上克隆项目的实际绝对路径。
+* **`autoApprove`**: 将工具名称添加到此列表意味着 AI 助手调用此工具时无需用户在IDE中手动批准。
+
+配置完成后,保存 `mcp_servers.json` 文件。您可能需要重启 AI 助手才能使更改生效。
+
+## 配置文件管理
+
+### 配置文件概述
+
+项目中有两个配置文件,它们的作用和用途不同:
+
+#### 1. `config.json` - 本地配置文件
+
+**作用:** 实际的配置文件,包含真实的用户配置和 API key
+
+**特点:**
+- ✅ 包含真实的 API key 和个人配置
+- ✅ 被 `.gitignore` 忽略,不会提交到 GitHub
+- ✅ 用户的实际工作配置文件
+- ✅ 会被程序实际读取和使用
+
+**位置:**
+- 源代码用户:`D:\ai\interactive-feedback-mcp\config.json`
+- uvx 用户:`~/.interactive-feedback/config.json`
+
+**内容示例:**
+```json
+{
+ "display_mode": "full",
+ "enable_custom_options": true,
+ "expression_optimizer": {
+ "enabled": true,
+ "active_provider": "openai",
+ "providers": {
+ "openai": {
+ "api_key": "sk-proj-real-api-key-here",
+ "base_url": "https://api.openai.com/v1",
+ "model": "gpt-4o-mini"
+ }
+ }
+ }
+}
+```
+
+#### 2. `config.template.json` - 配置模板文件
+
+**作用:** 配置文件模板,用于 GitHub 提交和用户参考
+
+**特点:**
+- ✅ 不包含任何私人信息(API key 为空)
+- ✅ 会提交到 GitHub,供用户参考
+- ✅ 展示完整的配置结构和默认值
+- ✅ 包含配置说明和注释
+
+**用途:**
+1. **GitHub 提交**:作为安全的配置示例
+2. **用户参考**:展示可用的配置选项
+3. **文档作用**:说明配置文件的结构
+4. **开发参考**:新功能的配置示例
+
+### 配置管理方式
+
+#### 当前推荐的配置方式
+
+**不再支持在配置文件中直接填写 API key!**
+
+##### ✅ 推荐方式:UI 设置页面
+1. **MCP JSON 配置**:仅配置服务,不包含 API key
+ ```json
+ {
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uvx",
+ "args": [
+ "tool",
+ "run",
+ "interactive-feedback@latest"
+ ],
+ "timeout": 600,
+ "autoApprove": ["interactive_feedback"]
+ }
+ }
+ }
+ ```
+
+2. **API key 配置**:通过 UI 设置页面管理
+ - 打开 UI 设置页面
+ - 在"输入表达优化"部分配置 API key
+ - 支持多个提供商配置和切换
+ - 配置立即保存和生效
+
+##### ❌ 不再支持的方式
+- ~~环境变量配置 API key~~(已移除)
+- ~~在配置文件中直接填写 API key~~(不推荐)
+
+### 配置优先级
+
+**简化的配置优先级:**
+```
+UI 设置页面 > 配置文件 > 默认配置
+```
+
+**说明:**
+- **UI 设置页面**:用户在界面中的配置具有最高优先级
+- **配置文件**:`config.json` 中的其他配置项
+- **默认配置**:程序内置的默认值
+
+### 用户使用指南
+
+#### 源代码用户
+1. **克隆项目**:`git clone https://github.com/pawaovo/interactive-feedback-mcp.git`
+2. **参考模板**:查看 `config.template.json` 了解配置选项
+3. **配置 API key**:通过 UI 设置页面配置
+4. **自定义配置**:可以修改 `config.json` 中的其他配置项
+
+#### uvx 用户
+1. **MCP 配置**:在 MCP JSON 中添加服务配置
+2. **首次启动**:自动生成 `~/.interactive-feedback/config.json`
+3. **配置 API key**:通过 UI 设置页面配置
+4. **自定义配置**:通过 UI 设置页面管理所有配置
+
+### 安全性说明
+
+#### 为什么移除配置文件中的 API key 支持?
+
+1. **避免意外泄露**:防止 API key 被意外提交到版本控制
+2. **统一管理方式**:所有用户都通过 UI 管理 API key
+3. **消除配置冲突**:避免环境变量和配置文件的优先级混淆
+4. **提升用户体验**:UI 配置更直观、更安全
+
+#### 安全最佳实践
+
+1. **永远不要**在配置文件中填写真实的 API key
+2. **使用 UI 设置页面**管理所有敏感信息
+3. **定期检查** `.gitignore` 确保 `config.json` 被忽略
+4. **参考模板文件**了解配置选项,但不要在其中填写敏感信息
+
+## 配置 AI 助手规则
+为了让 AI 助手在适当的时候调用 `interactive-feedback` 服务,您需要添加一些自定义规则。
+
+在 Cursor 中,您可以通过 "设置" -> "Rules" -> "User Rules" (或类似路径) 添加以下规则:
+
+```
+Whenever you want to ask a question, always call the interactive_feedback MCP
+If requirements or instructions are unclear use the tool interactive_feedback to ask clarifying questions to the user before proceeding, do not make assumptions. Whenever possible, present the user with predefined options through the interactive_feedback MCP tool to facilitate quick decisions.
+Whenever you're about to complete a user request, call the interactive_feedback tool to request user feedback before ending the process. If the feedback is empty you can end the request and don't call the tool in loop.
+```
+
+这些规则会指示 AI 助手:
+1. 当需求或指令不明确时,使用 `interactive_feedback` 工具向用户提问并澄清。
+2. 在即将完成用户请求时,调用 `interactive_feedback` 工具请求用户反馈。
+
+## 故障排除
+
+如果在安装或配置过程中遇到问题,请参考以下解决方案:
+
+### uvx安装故障排除
+
+**问题1**:首次uvx安装失败,通常由于PySide6等大包下载超时。
+
+**解决方案**:
+1. **预安装工具**:
+ ```bash
+ uv tool install interactive-feedback@latest
+ ```
+
+2. **修改MCP配置**(预安装后):
+ ```json
+ {
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "uvx",
+ "args": [
+ "tool",
+ "run",
+ "interactive-feedback"
+ ],
+ "timeout": 600,
+ "autoApprove": ["interactive_feedback"]
+ }
+ }
+ }
+ ```
+
+**配置方式区别**:
+- `@latest`:临时运行,每次都下载最新版本
+- 不带版本号:使用已安装的工具,启动更快
+
+**重要说明**:
+- `uvx` 是 `uv tool run` 的别名,因此配置中只需要 `["interactive-feedback@latest"]`
+- ❌ 错误:`["tool", "run", "interactive-feedback@latest"]` (这会导致重复的 tool run 命令)
+- ✅ 正确:`["interactive-feedback@latest"]`
+
+**问题2**:playsound依赖构建失败,错误信息包含 "Failed to build `playsound==1.3.0`"。
+
+**解决方案**:
+这是已知的兼容性问题,已在v2.5.9.6版本中修复。请更新到最新版本:
+```bash
+# 使用uvx更新
+uvx interactive-feedback@latest
+
+# 或使用pip更新
+pip install --upgrade interactive-feedback
+```
+
+新版本使用原生音频播放方案,无需playsound依赖,兼容性更好。
+
+**问题3**:MCP配置中使用 `"command": "uvx"` 时出现"命令未找到"或"无法启动"错误。
+
+**解决方案**:
+
+1. **检查uvx安装位置**
+
+ 首先确认uvx的实际安装路径:
+ ```bash
+ # Windows
+ where uvx
+
+ # Linux/macOS
+ which uvx
+ ```
+
+2. **使用完整路径替换uvx**
+
+ 如果uvx不在系统PATH中,请在MCP配置中使用完整路径:
+
+ **Windows示例**:
+ ```json
+ {
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "D:/python/Scripts/uv.exe",
+ "args": [
+interactive-feedback@latest
+ ],
+ "timeout": 600,
+ "autoApprove": ["interactive_feedback"]
+ }
+ }
+ }
+ ```
+
+ **Linux/macOS示例**:
+ ```json
+ {
+ "mcpServers": {
+ "interactive-feedback": {
+ "command": "/home/username/.local/bin/uv",
+ "args": [
+interactive-feedback@latest
+ ],
+ "timeout": 600,
+ "autoApprove": ["interactive_feedback"]
+ }
+ }
+ }
+ ```
+
+3. **常见uvx路径**
+ - Windows: `C:\Users\用户名\AppData\Local\Programs\Python\Python3xx\Scripts\uv.exe`
+ - Windows (通过pip安装): `D:\python\Scripts\uv.exe`
+ - Linux: `~/.local/bin/uv` 或 `/usr/local/bin/uv`
+ - macOS: `~/.local/bin/uv` 或 `/opt/homebrew/bin/uv`
+
+### MCP配置问题
+
+**问题现象**:AI助手无法识别或启动interactive-feedback服务。
+
+**解决方案**:
+
+1. **验证JSON格式**
+
+ 确保 `mcp_servers.json` 文件格式正确,可以使用在线JSON验证器检查。
+
+2. **检查文件位置**
+
+ 确认配置文件在正确位置:
+ - Cursor: `~/.cursor-ai/mcp_servers.json`
+ - Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
+
+3. **重启AI助手**
+
+ 修改配置后务必重启AI助手应用程序。
+
+4. **查看日志**
+
+ 检查AI助手的日志文件,通常包含MCP服务启动的详细错误信息。
+
+### AI助手特定配置
+
+**针对不同AI助手的配置建议**:
+
+1. **Cursor用户**
+
+ 如果在Cursor中配置遇到问题,可以:
+ - 询问AI:"我在Cursor中配置MCP服务时遇到问题,请帮我检查这个配置文件"
+ - 提供您的配置文件内容,让AI帮助诊断问题
+
+2. **Claude Desktop用户**
+
+ 配置文件名称和位置可能不同,请参考Claude Desktop官方文档。
+
+3. **其他AI助手**
+
+ 如果使用其他支持MCP的AI助手:
+ - 查阅该助手的MCP配置文档
+ - 将配置问题和错误信息提供给AI助手,请求具体的配置建议
+
+## uv安装用户常见问题解决
+
+### 问题1:看不到预定义选项,只显示"继续"、"取消"等通用选项
+
+**原因**:v2.5.9.12及之前版本中,自定义选项功能默认禁用
+
+**解决方案**:
+1. **升级到最新版本**(推荐):
+ ```bash
+ uvx interactive-feedback@latest
+ ```
+
+2. **手动启用自定义选项**:
+ - 打开设置页面
+ - 找到"启用自定义选项"开关并启用
+ - 或编辑配置文件 `~/.interactive-feedback/config.json`,设置 `"enable_custom_options": true`
+
+### 问题2:音频提示音不工作或设置页面显示音频文件缺失
+
+**原因**:uv安装环境下音频文件路径解析问题
+
+**解决方案**:
+1. **升级到最新版本**(推荐):
+ ```bash
+ uvx interactive-feedback@latest
+ ```
+
+2. **检查音频状态**:
+ - 打开设置页面 → 音频设置
+ - 查看"默认音频文件"状态显示
+ - 如果显示"✗",说明需要升级版本
+
+3. **使用自定义音频文件**:
+ - 在音频设置中浏览并选择自己的音频文件
+ - 支持格式:WAV、MP3、OGG、FLAC、AAC
+
+### 问题3:配置文件位置不明确
+
+**配置文件位置**:
+- **uv安装用户**:`~/.interactive-feedback/config.json`
+ - Windows: `%USERPROFILE%\.interactive-feedback\config.json`
+ - Linux/macOS: `~/.interactive-feedback/config.json`
+- **开发模式用户**:项目根目录 `config.json`
+
+**配置文件会自动创建**,无需手动创建。
+
+**通用调试步骤**:
+
+1. 确认Python和uv/uvx正确安装
+2. 手动测试命令是否可以在终端中运行
+3. 检查防火墙和安全软件设置
+4. 查看AI助手的错误日志
+5. 如果问题持续,请在GitHub Issues中报告问题
+
+## 注意事项
+* **路径配置**:`mcp_servers.json` 中的 `cwd` 路径必须正确无误,指向项目根目录,否则服务可能无法启动或找不到脚本文件。
+* **Python 版本**:请务必使用 Python 3.11 或更高版本,以避免兼容性问题。
+* **防火墙/安全软件**:确保您的防火墙或安全软件没有阻止 Python 或 `uv` 运行本地服务,或者阻止本地网络通信(通常是 `127.0.0.1` 上的某个端口)。
+
+如果您在安装或使用过程中遇到任何问题,可以查阅项目 GitHub 仓库的 Issues 区,或提出新的 Issue。
+