diff --git a/README.en.md b/README.en.md index 66824e3..31acaee 100644 --- a/README.en.md +++ b/README.en.md @@ -14,7 +14,7 @@ ## ✨ Features -- 🤖 **Multi AI Engines** — Ollama, MiMo, GLM, QwQ, MaaS and more +- 🤖 **Multi AI Engines** — Ollama local models + OpenAI compatible format (DeepSeek, Grok, SiliconFlow, etc.) - 🔧 **Precise Code Editing** — Search & replace, batch edits, diff comparison, line-level operations - 🔍 **Code Search & Understanding** — Cross-file search, regex matching, symbol extraction, dependency analysis - 🔄 **Git Integration** — Full Git workflow support @@ -43,7 +43,7 @@ python -m xiaoli python -m xiaoli --cli # Specify engine -python -m xiaoli --engine mimo +python -m xiaoli --engine openai # Show version python -m xiaoli --version diff --git a/README.md b/README.md index 71e80c6..fe1246e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ## ✨ 特性 -- 🤖 **多 AI 引擎** — 支持 Ollama、MiMo、智谱 GLM、QwQ、MaaS 等,随时切换 +- 🤖 **多 AI 引擎** — 支持 Ollama 本地模型 + OpenAI 兼容格式(DeepSeek、Grok、硅基流动等),随时切换 - 🔧 **精准代码编辑** — 搜索替换、批量编辑、diff 对比、行级操作 - 🔍 **代码搜索理解** — 跨文件搜索、正则匹配、符号提取、依赖分析 - 🔄 **Git 版本控制** — 完整 Git 操作支持 @@ -46,7 +46,7 @@ python -m xiaoli python -m xiaoli --cli # 指定引擎 -python -m xiaoli --engine mimo +python -m xiaoli --engine openai # 查看版本 python -m xiaoli --version @@ -112,12 +112,7 @@ xiaoli-cli/ │ ├── ai_engines/ # AI 引擎 │ ├── ollama_engine.py # Ollama 本地模型 -│ ├── mimo_engine.py # 小米 MiMo -│ ├── GLM_http_engine.py # 智谱 GLM -│ ├── qwq_engine.py # QwQ 深度思考 -│ ├── xfyun_spark_engine.py # 讯飞星火 -│ ├── SDK_openAI.py # OpenAI 兼容 SDK -│ └── ... +│ └── openai_engine.py # OpenAI 兼容格式(DeepSeek/Grok/硅基流动等) │ ├── image_engine/ # 图像引擎 ├── core/ # 旧版核心模块(兼容) @@ -196,12 +191,23 @@ def new(): | 引擎 | 类型 | 说明 | |------|------|------| | `ollama` | 本地 | Ollama 本地模型,无需 API 密钥 | -| `mimo` | 云端 | 小米 MiMo | -| `glm_http` | 云端 | 智谱 GLM | -| `qwq` | 云端 | QwQ 深度思考引擎 | -| `maas_http` | 云端 | MaaS | -| `spark` | 云端 | 讯飞星火 | -| `iflow` | 云端 | iFlow API | +| `openai` | 云端/本地 | OpenAI 兼容格式,支持 DeepSeek、Grok、硅基流动、本地 vLLM 等 | + +#### OpenAI 兼容引擎配置示例 + +```json +{ + "api": { + "engines": { + "openai": { + "api_key": "你的API密钥", + "base_url": "https://api.deepseek.com/v1", + "model": "deepseek-chat" + } + } + } +} +``` ### 远程连接 diff --git a/ai_engines/GLM_http_engine.py b/ai_engines/GLM_http_engine.py deleted file mode 100644 index 0c990e8..0000000 --- a/ai_engines/GLM_http_engine.py +++ /dev/null @@ -1,210 +0,0 @@ -import os -import json -import requests -from colorama import Fore, Style -from openai import OpenAI - -# 获取项目根目录 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) - - -class Glm_http_engineAI: - """基于HTTP协议的智谱AI GLM引擎""" - requires_api_key = True # GLM HTTP引擎需要API密钥 - - def get_help_info(self): - """获取引擎帮助信息""" - help_text = """ -GLM HTTP AI引擎插件帮助信息 -========================== - -引擎名称: glm_http -描述: 基于HTTP协议的智谱AI GLM(GLM-4.5-Flash)引擎,支持智谱AI服务 - -可用命令: - 无特定命令,直接与AI对话即可 - -使用说明: -1. 此引擎使用HTTP协议调用智谱AI API -2. 使用/engine.switch glm_http切换到此引擎 -3. 直接输入自然语言与AI对话 -4. 支持普通对话、流式对话、JSON模式等多种调用方式 -""" - return help_text - - def __init__(self): - self.name = "glm_http" - self.cli = None - - # 直接从 config.json 读取配置 - config_file = os.path.join(project_root, "config.json") - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get("GLM_http", {}) - self.api_key = engine_config.get("api_key", "") - self.base_url = engine_config.get("base_url", "") - self.model_id = engine_config.get("model", "") - except Exception as e: - # 如果读取失败,使用空值 - self.api_key = "" - self.base_url = "" - self.model_id = "" - - # 初始化对话历史 - self.conversation_history = [] - # 共享对话历史引用(由CLI设置) - self.shared_conversation_history = None - # 设置最大历史记录数 - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - self.max_history = config.get("system", {}).get("max_history", 10) - except: - self.max_history = 10 - - # 初始化 OpenAI 客户端(配置从 config.json 动态读取) - try: - self.client = OpenAI( - api_key=self.api_key, - base_url=self.base_url if self.base_url else None, - timeout=None - ) - except Exception as e: - print(f"初始化 OpenAI 客户端失败: {e}") - self.client = None - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成AI響應 - 使用智谱AI HTTP API""" - try: - # 如果有工具結果,將其作為上下文返回給AI處理 - if tool_results: - tool_result_text = f"工具執行結果: {tool_results.get('result', '無結果')}" - - # 構建包含歷史記錄的消息列表 - messages = self._build_messages_with_history(system_prompt, tool_result_text) - - # 調用API - response = self._call_api(messages) - - if response: - return response - else: - return "智谱AI HTTP API調用失敗" - - # 構建包含歷史記錄的消息列表 - messages = self._build_messages_with_history(system_prompt, user_input) - - # 調用API - response = self._call_api(messages) - - if response: - return response - else: - return "智谱AI HTTP API調用失敗" - - except Exception as e: - # 如果API調用失敗,返回錯誤信息 - return f"智谱AI HTTP API調用失敗: {str(e)}" - - def _call_api(self, messages, use_stream=False, extra_body=None): - """調用API的核心方法""" - if extra_body is None: - extra_body = {} - - try: - response = self.client.chat.completions.create( - model=self.model_id, - messages=messages, - stream=use_stream, - temperature=0.7, - max_tokens=4096, - extra_headers={"lora_id": "0"}, # 調用微調大模型時,對應替換為模型服務卡片上的resourceId - stream_options={"include_usage": True}, - extra_body=extra_body - ) - - if use_stream: - # 處理流式響應 - full_response = "" - for chunk in response: - if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: - content = chunk.choices[0].delta.content - full_response += content - return full_response - else: - # 處理非流式響應 - return response.choices[0].message.content - - except Exception as e: - print(f"智谱AI HTTP API請求出錯: {e}") - return None - - def _build_messages_with_history(self, system_prompt, user_input): - """構建包含歷史記錄的消息列表""" - messages = [] - - # 添加系統提示詞 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加歷史對話記錄(使用共享歷史) - if self.shared_conversation_history is not None: - # 確保歷史記錄格式正確 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正確,跳過該條記錄 - continue - - # 添加當前用戶輸入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - def _call_api_with_history(self, content, system_prompt=None): - """使用對話歷史調用API""" - # 構建消息列表 - messages = [] - # 添加系統提示詞(如果提供) - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - # 添加歷史對話記錄(使用共享歷史) - if self.shared_conversation_history is not None: - # 確保歷史記錄格式正確 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正確,跳過該條記錄 - continue - # 添加當前內容 - messages.append({ - "role": "tool", - "content": content - }) - - # 調用API - response = self._call_api(messages) - - if response: - return response - else: - return "智谱AI HTTP API調用失敗" - - def handle_command(self, command): - """處理引擎特定命令""" - # GLM HTTP引擎目前不支持特定命令 - print(f"{Fore.RED}GLM HTTP引擎不支持此命令{Style.RESET_ALL}") - return False \ No newline at end of file diff --git a/ai_engines/SDK_openAI.py b/ai_engines/SDK_openAI.py deleted file mode 100644 index b2dba98..0000000 --- a/ai_engines/SDK_openAI.py +++ /dev/null @@ -1,266 +0,0 @@ -""" -小狸 Pro-CLI AI 引擎开发 SDK -提供快速开发 AI 引擎的工具包 -""" -import os -import sys -import json -import requests -from colorama import Fore, Style -from openai import OpenAI -import logging - -# 设置日志 -logger = logging.getLogger(__name__) - -# 添加项目根目录到sys.path -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) -sys.path.insert(0, project_root) - - -class _AIEngineSDK: - """AI 引擎 SDK 内部实现""" - - def __init__(self): - self.name = "" - self.api_key = "" - self.base_url = "" - self.model = "" - self.is_online = True - self.max_history = 10 - self.conversation_history = [] - self.shared_conversation_history = None - self.cli = None - - # 深度思考引擎配置 - self.is_thinking_engine = False # 是否是深度思考引擎 - self.thinking_start_marker = "" # 思考开始标记 - self.thinking_end_marker = "" # 思考结束标记 - self.show_thinking = True # 是否显示思考过程 - - # 配置文件路径 - self.config_file = os.path.join(project_root, "config.json") - - def _load_config(self): - """从配置文件加载配置""" - try: - with open(self.config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get(self.name, {}) - - if not self.api_key: - self.api_key = engine_config.get("api_key", "") - if not self.base_url: - self.base_url = engine_config.get("base_url", "") - if not self.model: - self.model = engine_config.get("model", "") - - self.max_history = config.get("system", {}).get("max_history", 10) - except Exception as e: - logger.error(f"加载配置失败: {e}") - self.max_history = 10 - - def _call_openai_api(self, messages): - """调用 OpenAI 格式的 API""" - try: - self._load_config() - - # 构建请求体 - body = { - "model": self.model, - "messages": messages, - "stream": False - } - - # 构建请求头 - headers = { - 'Content-Type': 'application/json' - } - - if self.api_key: - headers['Authorization'] = f'Bearer {self.api_key}' - - # 确定 API 地址 - api_url = self.base_url - if not api_url.endswith("/chat/completions"): - api_url = api_url.rstrip('/') + "/chat/completions" - - # 发送请求 - response = requests.post( - url=api_url, - json=body, - headers=headers, - timeout=None - ) - - # 处理响应 - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 如果是深度思考引擎,处理思考内容 - if self.is_thinking_engine: - ai_response = self._process_thinking_content(ai_response) - - return ai_response - else: - return f"AI 返回格式错误: {response_data}" - else: - return f"AI 调用失败: {response.status_code} - {response.text}" - - except Exception as e: - return f"AI 调用失败: {str(e)}" - - def _build_messages(self, system_prompt, user_input): - """构建消息列表""" - messages = [] - - # 添加系统提示词 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加历史对话记录 - if self.shared_conversation_history is not None: - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - - # 添加当前用户输入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成 AI 响应""" - try: - # 如果有工具结果,将其作为上下文返回给AI处理 - if tool_results: - tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}" - messages = self._build_messages(system_prompt, tool_result_text) - else: - messages = self._build_messages(system_prompt, user_input) - - # 调用 API - return self._call_openai_api(messages) - - except Exception as e: - return f"AI 调用失败: {str(e)}" - - def _process_thinking_content(self, response): - """处理思考内容,将思考部分以灰色文本显示,只返回回复部分""" - import re - - if not self.thinking_start_marker or not self.thinking_end_marker: - # 如果没有设置标记,直接返回 - return response - - # 使用正则表达式查找思考内容 - pattern = f"{re.escape(self.thinking_start_marker)}(.*?){re.escape(self.thinking_end_marker)}" - matches = re.findall(pattern, response, re.DOTALL) - - if matches and self.show_thinking: - for match in matches: - # 用灰色文本显示思考内容 - thinking_text = f"{Fore.LIGHTBLACK_EX}[思考: {match}]{Style.RESET_ALL}" - # 替换思考标记之间的内容,保留思考内容但以灰色显示 - response = re.sub(pattern, thinking_text, response, 1) - elif matches and not self.show_thinking: - # 如果不显示思考内容,直接删除 - for match in matches: - response = re.sub(pattern, "", response, 1) - - return response - - -# 全局 SDK 实例 -_sdk_instance = _AIEngineSDK() - - -def engine_Online(mode): - """ - 设置引擎模式 - - Args: - mode: "on" - 云模式(在线 API) - "off" - 本地模式(如 Ollama) - """ - _sdk_instance.is_online = (mode.lower() == "on") - - -def ai_name(name): - """设置引擎名称""" - _sdk_instance.name = name - - -def ai_api_url(url): - """设置 API 地址""" - _sdk_instance.base_url = url - - -def ai_api_key(key): - """设置 API 密钥""" - _sdk_instance.api_key = key - - -def ai_model_id(model): - """设置模型名称""" - _sdk_instance.model = model - - -def ai_thinking_engine(enable=True, start_marker="", end_marker="", show_thinking=True): - """ - 设置深度思考引擎配置 - - Args: - enable: 是否启用深度思考引擎模式 - start_marker: 思考开始标记,默认为 "" - end_marker: 思考结束标记,默认为 "" - show_thinking: 是否显示思考过程,默认为 True - """ - _sdk_instance.is_thinking_engine = enable - _sdk_instance.thinking_start_marker = start_marker - _sdk_instance.thinking_end_marker = end_marker - _sdk_instance.show_thinking = show_thinking - - -def set_cli(cli): - """设置 CLI 引用""" - _sdk_instance.cli = cli - - -def set_shared_history(history): - """设置共享对话历史""" - _sdk_instance.shared_conversation_history = history - - -def generate_response(user_input, tool_results=None, system_prompt=None): - """生成 AI 响应""" - return _sdk_instance.generate_response(user_input, tool_results, system_prompt) - - -def get_sdk_instance(): - """获取 SDK 实例(用于高级用法)""" - return _sdk_instance - - -# 导出 -__all__ = [ - 'engine_Online', - 'ai_name', - 'ai_api_url', - 'ai_api_key', - 'ai_model_id', - 'ai_thinking_engine', - 'set_cli', - 'set_shared_history', - 'generate_response', - 'get_sdk_instance' -] \ No newline at end of file diff --git a/ai_engines/iflow_api_engine.py b/ai_engines/iflow_api_engine.py deleted file mode 100644 index 8f68d35..0000000 --- a/ai_engines/iflow_api_engine.py +++ /dev/null @@ -1,405 +0,0 @@ -import os -import sys -import json -import requests -import threading -import time -from colorama import Fore, Style -import logging - -# 设置日志 -logger = logging.getLogger(__name__) - -# 添加项目根目录到sys.path,以便正确导入 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) # 获取项目根目录 -sys.path.insert(0, project_root) - - -class IflowAPIAI: - """基于Qwen3-Coder-Plus模型的AI引擎""" - - def get_help_info(self): - """获取引擎帮助信息""" - help_text = """ -iflow API AI引擎插件帮助信息 -========================== - -引擎名称: iflow-api -描述: 基于Qwen3-Coder-Plus模型的AI引擎,支持复杂推理和代码生成 - -可用命令: - 无特定命令,直接与AI对话即可 - -使用说明: -1. 此引擎使用Qwen3-Coder-Plus云API -2. 使用/engine.switch iflow-api切换到此引擎 -3. 直接输入自然语言与AI对话 -4. 特别适合处理代码生成、技术问题等复杂任务 -""" - return help_text - - def __init__(self): - self.name = "iflow-api" - self.cli = None # 引用CLI实例以访问插件 - - # 直接从 config.json 读取配置(一次性读取) - project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - config_file = os.path.join(project_root, "config.json") - - # 默认值 - self.api_key = "" - self.url = "" - self.model = "" - self.max_history = 10 - - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get("iflow_api", {}) - self.api_key = engine_config.get("api_key", "") - self.url = engine_config.get("base_url", "") - self.model = engine_config.get("model", "") - self.max_history = config.get("system", {}).get("max_history", 10) - except Exception as e: - # 如果读取失败,使用默认值 - logger.warning(f"警告: 读取配置文件失败: {e}") - print(f"{Fore.YELLOW}警告: 读取配置文件失败: {e}{Style.RESET_ALL}") - - # 验证API密钥 - if not self.api_key: - logger.warning("警告: iflow-api引擎的API密钥未设置") - print(f"{Fore.YELLOW}警告: iflow-api引擎的API密钥未设置{Style.RESET_ALL}") - - self.headers = { - 'Authorization': f'Bearer {self.api_key}', - 'Content-Type': 'application/json' - } - - # 初始化对话历史 - self.conversation_history = [] - - # 设置思考标记 - self.thinking_start_marker = "" - self.thinking_end_marker = "" - - # 共享对话历史引用(由CLI设置) - self.shared_conversation_history = None - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成AI响应 - 使用 qwen3-coder-plus""" - try: - # 如果有工具结果,将其作为上下文返回给AI处理 - if tool_results: - tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}" - # 构建消息列表 - - messages = self._build_messages_with_history(system_prompt, tool_result_text) - - - - # 构建请求体 - - body = { - - "model": self.model or "qwen3-coder-plus", - - "messages": messages, - - "stream": False # 使用非流式响应以便于处理 - - } - - - - # 发送请求到qwen3-coder-plus API - - response = requests.post(url=self.url, json=body, headers=self.headers, timeout=None) - - - - if response.status_code == 200: - - response_data = response.json() - - # 检查是否是API错误响应(例如认证失败) - if isinstance(response_data, dict) and "status" in response_data: - # 检查是否是认证错误 - if response_data.get("status") in ["434", "401", "403"]: - error_msg = response_data.get("msg", "API认证失败") - return f"qwen3-coder-plus API错误: {error_msg}" - elif "body" in response_data and response_data["body"] is None: - error_msg = response_data.get("msg", "API请求失败") - return f"qwen3-coder-plus API错误: {error_msg}" - - if "choices" in response_data and len(response_data["choices"]) > 0: - - # 改进错误处理以适应不同的API响应格式 - choice = response_data["choices"][0] - if "message" in choice and "content" in choice["message"]: - ai_response = choice["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - # 返回处理后的AI回复 - return processed_response - else: - # 尝试其他可能的字段名 - if "text" in choice: - return choice["text"] - elif "delta" in choice and "content" in choice["delta"]: - return choice["delta"]["content"] - else: - return f" qwen3-coder-plus返回格式错误: 缺少预期的响应字段 - {response_data}" - - else: - - return f" qwen3-coder-plus返回格式错误: {response_data}" - - else: - - return f" qwen3-coder-plus调用失败: {response.status_code} - {response.text}" - - - - # 构建消息列表 - - messages = self._build_messages_with_history(system_prompt, user_input) - - - - # 构建请求体 - - body = { - - "model": self.model or "qwen3-coder-plus", - - "messages": messages, - - "stream": False # 使用非流式响应以便于处理 - - } - - - - # 发送请求到qwen3-coder-plus - - response = requests.post(url=self.url, json=body, headers=self.headers, timeout=None) - - - - if response.status_code == 200: - - response_data = response.json() - - # 检查是否是API错误响应(例如认证失败) - if isinstance(response_data, dict) and "status" in response_data: - # 检查是否是认证错误 - if response_data.get("status") in ["434", "401", "403"]: - error_msg = response_data.get("msg", "API认证失败") - return f"qwen3-coder-plus API错误: {error_msg}" - elif "body" in response_data and response_data["body"] is None: - error_msg = response_data.get("msg", "API请求失败") - return f"qwen3-coder-plus API错误: {error_msg}" - - if "choices" in response_data and len(response_data["choices"]) > 0: - - # 改进错误处理以适应不同的API响应格式 - choice = response_data["choices"][0] - if "message" in choice and "content" in choice["message"]: - ai_response = choice["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - # 返回处理后的AI回复 - return processed_response - else: - # 尝试其他可能的字段名 - if "text" in choice: - return choice["text"] - elif "delta" in choice and "content" in choice["delta"]: - return choice["delta"]["content"] - else: - return f"qwen3-coder-plus返回格式错误: 缺少预期的响应字段 - {response_data}" - - else: - - return f"qwen3-coder-plus返回格式错误: {response_data}" - - else: - - return f"qwen3-coder-plus调用失败: {response.status_code} - {response.text}" - - - - except Exception as e: - - # 如果API调用失败,返回错误信息 - - return f"qwen3-coder-plus调用失败: {str(e)}" - - def _build_messages_with_history(self, system_prompt, user_input): - """构建包含历史记录的消息列表""" - messages = [] - - # 添加系统提示词 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加历史对话记录(使用共享历史) - if self.shared_conversation_history is not None: - # 确保历史记录格式正确 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正确,跳过该条记录 - continue - - # 添加当前用户输入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - - - def _call_api_with_history(self, content, system_prompt=None): - """使用对话历史调用API""" - # 构建消息列表 - - messages = [] - - # 添加系统提示词(如果提供) - - if system_prompt: - - messages.append({ - - "role": "system", - - "content": system_prompt - - }) - - # 添加历史对话记录(使用共享历史) - - if self.shared_conversation_history is not None: - - # 确保历史记录格式正确 - - for msg in self.shared_conversation_history[-self.max_history:]: - - if isinstance(msg, dict) and "role" in msg and "content" in msg: - - messages.append(msg) - - else: - - # 如果格式不正确,跳过该条记录 - - continue - - # 添加当前内容 - - messages.append({ - - "role": "tool", - - "content": content - - }) - - - - # 构建请求体 - body = { - "model": self.model or "qwen3-coder-plus", - "messages": messages, - "stream": False # 使用非流式响应以便于处理 - } - - - # 发送请求到qwen3-coder-plus - - response = requests.post(url=self.url, json=body, headers=self.headers, timeout=None) - - - - if response.status_code == 200: - - response_data = response.json() - - # 检查是否是API错误响应(例如认证失败) - if isinstance(response_data, dict) and "status" in response_data: - # 检查是否是认证错误 - if response_data.get("status") in ["434", "401", "403"]: - error_msg = response_data.get("msg", "API认证失败") - return f"qwen3-coder-plus API错误: {error_msg}" - elif "body" in response_data and response_data["body"] is None: - error_msg = response_data.get("msg", "API请求失败") - return f"qwen3-coder-plus API错误: {error_msg}" - - if "choices" in response_data and len(response_data["choices"]) > 0: - - # 改进错误处理以适应不同的API响应格式 - choice = response_data["choices"][0] - if "message" in choice and "content" in choice["message"]: - ai_response = choice["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - return processed_response - else: - # 尝试其他可能的字段名 - if "text" in choice: - return choice["text"] - elif "delta" in choice and "content" in choice["delta"]: - return choice["delta"]["content"] - else: - return f"qwen3-coder-plus返回格式错误: 缺少预期的响应字段 - {response_data}" - - else: - - return f"qwen3-coder-plus返回格式错误: {response_data}" - - else: - - return f"qwen3-coder-plus调用失败: {response.status_code} - {response.text}" - - def handle_command(self, command): - """处理引擎特定命令""" - # qwen3-coder-plus引擎目前不支持特定命令 - print(f"{Fore.RED}qwen3-coder-plus引擎不支持此命令{Style.RESET_ALL}") - return False - - def _process_thinking_content(self, response): - """处理思考内容,将思考部分以灰色文本显示,只返回回复部分""" - from colorama import Fore, Style - import re - - # 这里可以设置思考标记,由用户自己定义 - thinking_start_marker = "" - thinking_end_marker = "" - - if self.thinking_start_marker and self.thinking_end_marker: - # 使用正则表达式查找思考内容 - pattern = f"{re.escape(self.thinking_start_marker)}(.*?){re.escape(self.thinking_end_marker)}" - matches = re.findall(pattern, response, re.DOTALL) - - if matches: - for match in matches: - # 用灰色文本显示思考内容 - thinking_text = f"{Fore.LIGHTBLACK_EX}[思考: {match}]{Style.RESET_ALL}" - # 替换思考标记之间的内容,保留思考内容但以灰色显示 - response = re.sub(pattern, thinking_text, response, 1) - - return response \ No newline at end of file diff --git a/ai_engines/maas_http_engine.py b/ai_engines/maas_http_engine.py deleted file mode 100644 index 5ae5446..0000000 --- a/ai_engines/maas_http_engine.py +++ /dev/null @@ -1,205 +0,0 @@ -import os -import json -import requests -from colorama import Fore, Style -from openai import OpenAI - -# 获取项目根目录 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) - - -class Maas_http_engineAI: - """基于HTTP协议的MaaS(Model as a Service)AI引擎""" - requires_api_key = True # MaaS HTTP引擎需要API密钥 - - def get_help_info(self): - """获取引擎帮助信息""" - help_text = """ -MaaS HTTP AI引擎插件帮助信息 -========================== - -引擎名称: maas_http -描述: 基于HTTP协议的MaaS(Model as a Service)AI引擎,支持多种大模型推理服务 - -可用命令: - 无特定命令,直接与AI对话即可 - -使用说明: -1. 此引擎使用HTTP协议调用MaaS API -2. 使用/engine.switch maas_http切换到此引擎 -3. 直接输入自然语言与AI对话 -4. 支持普通对话、流式对话、JSON模式等多种调用方式 -""" - return help_text - - def __init__(self): - self.name = "maas_http" - self.cli = None # 引用CLI实例以訪問插件 - - # 直接从 config.json 读取配置 - config_file = os.path.join(project_root, "config.json") - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get("maas_http", {}) - self.api_key = engine_config.get("api_key", "") - self.base_url = engine_config.get("base_url", "") - self.model_id = engine_config.get("model", "") - except Exception as e: - # 如果读取失败,使用空值 - self.api_key = "" - self.base_url = "" - self.model_id = "" - # 初始化客户端 - self.client = OpenAI( - api_key=self.api_key, - base_url=self.base_url - ) - - # 初始化对话历史 - self.conversation_history = [] - # 设置最大历史记录数 - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - self.max_history = config.get("system", {}).get("max_history", 10) - except: - self.max_history = 10 - - # 共享對話歷史引用(由CLI設置) - self.shared_conversation_history = None - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成AI響應 - 使用MaaS HTTP API""" - try: - # 如果有工具結果,將其作為上下文返回給AI處理 - if tool_results: - tool_result_text = f"工具執行結果: {tool_results.get('result', '無結果')}" - - # 構建包含歷史記錄的消息列表 - messages = self._build_messages_with_history(system_prompt, tool_result_text) - - # 調用API - response = self._call_api(messages) - - if response: - return response - else: - return "MaaS HTTP API調用失敗" - - # 構建包含歷史記錄的消息列表 - messages = self._build_messages_with_history(system_prompt, user_input) - - # 調用API - response = self._call_api(messages) - - if response: - return response - else: - return "MaaS HTTP API調用失敗" - - except Exception as e: - # 如果API調用失敗,返回錯誤信息 - return f"MaaS HTTP API調用失敗: {str(e)}" - - def _call_api(self, messages, use_stream=False, extra_body=None): - """調用API的核心方法""" - if extra_body is None: - extra_body = {} - - try: - response = self.client.chat.completions.create( - model=self.model_id or self.default_model, - messages=messages, - stream=use_stream, - temperature=0.7, - max_tokens=4096, - extra_headers={"lora_id": "0"}, - stream_options={"include_usage": True}, - extra_body=extra_body - ) - - if use_stream: - # 處理流式響應 - full_response = "" - for chunk in response: - if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content: - content = chunk.choices[0].delta.content - full_response += content - return full_response - else: - # 處理非流式響應 - return response.choices[0].message.content - - except Exception as e: - print(f"MaaS HTTP API請求出錯: {e}") - return None - - def _build_messages_with_history(self, system_prompt, user_input): - """構建包含歷史記錄的消息列表""" - messages = [] - - # 添加系統提示詞 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加歷史對話記錄(使用共享歷史) - if self.shared_conversation_history is not None: - # 確保歷史記錄格式正確 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正確,跳過該條記錄 - continue - - # 添加當前用戶輸入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - def _call_api_with_history(self, content, system_prompt=None): - """使用對話歷史調用API""" - # 構建消息列表 - messages = [] - # 添加系統提示詞(如果提供) - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - # 添加歷史對話記錄(使用共享歷史) - if self.shared_conversation_history is not None: - # 確保歷史記錄格式正確 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正確,跳過該條記錄 - continue - # 添加當前內容 - messages.append({ - "role": "tool", - "content": content - }) - - # 調用API - response = self._call_api(messages) - - if response: - return response - else: - return "MaaS HTTP API調用失敗" - - def handle_command(self, command): - """處理引擎特定命令""" - # MaaS HTTP引擎目前不支持特定命令 - print(f"{Fore.RED}MaaS HTTP引擎不支持此命令{Style.RESET_ALL}") - return False \ No newline at end of file diff --git a/ai_engines/mimo_engine.py b/ai_engines/mimo_engine.py deleted file mode 100644 index f8a23a2..0000000 --- a/ai_engines/mimo_engine.py +++ /dev/null @@ -1,355 +0,0 @@ -import os -import sys -import json -import requests -from colorama import Fore, Style -from openai import OpenAI -import logging -from datetime import datetime - -# 设置日志 -logger = logging.getLogger(__name__) - -# 添加项目根目录到sys.path -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) -sys.path.insert(0, project_root) - - -class MimoAI: - """小米MiMoAI引擎 - 使用MiMo-v2-flash模型 - OpenAI格式""" - requires_api_key = True # MiMo引擎需要API密钥 - - def get_help_info(self): - """获取引擎帮助信息""" - help_text = """ -MiMo AI引擎插件帮助信息 -========================== - -引擎名称: MiMo -描述: 基于MiMo模型的AI引擎,由小米公司开发 - -可用命令: - 无特定命令,直接与AI对话即可 - -使用说明: -1. 此引擎使用小米MiMo云API -2. 使用/engine.switch mimo切换到此引擎 -3. 直接输入自然语言与AI对话 -4. 支持思考模式和多轮对话 -""" - return help_text - - def __init__(self): - self.name = "mimo" - self.cli = None - - # 直接从 config.json 读取配置 - config_file = os.path.join(project_root, "config.json") - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get("mimo", {}) - self.api_key = engine_config.get("api_key", "") - base_url_raw = engine_config.get("base_url", "") - self.base_url = base_url_raw.rstrip('/') + '/chat/completions' if base_url_raw else "" - self.model = engine_config.get("model", "") - except Exception as e: - # 如果读取失败,使用空值 - self.api_key = "" - self.base_url = "" - self.model = "" - - # 初始化对话历史 - self.conversation_history = [] - # 设置最大历史记录数 - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - self.max_history = config.get("system", {}).get("max_history", 10) - except: - self.max_history = 10 - - # 初始化 headers(用于 API 请求) - self.headers = { - 'Authorization': f'Bearer {self.api_key}', - 'Content-Type': 'application/json' - } - - # 初始化 OpenAI 客户端 - try: - if self.api_key and self.base_url: - self.client = OpenAI( - api_key=self.api_key, - base_url=self.base_url.replace('/chat/completions', ''), - timeout=None - ) - else: - self.client = None - except Exception as e: - logger.warning(f"初始化 MiMo OpenAI 客户端失败: {e}") - self.client = None - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成AI响应 - 使用MiMo-v2-flash - OpenAI格式""" - if not self.client: - return "MiMo引擎未正确初始化,无法调用API" - - try: - # 获取当前日期和星期 - now = datetime.now() - date_str = now.strftime("%Y年%m月%d日") - week_str = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"][now.weekday()] - current_datetime = f"{date_str} {week_str}" - - # 设置默认系统提示词(如果未提供) - if not system_prompt: - system_prompt = f"You are MiMo, an AI assistant developed by Xiaomi. Today is date: {current_datetime}. Your knowledge cutoff date is December 2024." - - # 如果有工具结果,将其作为上下文返回给AI处理 - if tool_results: - tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}" - - # 构建包含历史记录的消息列表 - messages = self._build_messages_with_history(system_prompt, tool_result_text) - - # 构建请求体,使用MiMo特定参数 - body = { - "model": self.model, - "messages": messages, - "temperature": 0.7, - "max_tokens": 2048, - "top_p": 0.95, - "frequency_penalty": 0, - "presence_penalty": 0, - # MiMo特定参数 - "extra_body": { - "thinking": {"type": "disabled"} # 禁用思考模式 - } - } - - # 直接使用requests调用MiMo API,因为需要特定的header格式 - import requests - response = requests.post( - url=self.base_url, - json=body, - headers=self.headers, - timeout=None - ) - - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - # 更新对话历史 - self._update_conversation_history(user_input if not tool_results else tool_result_text, ai_response) - - # 返回处理后的AI回复,前面加上✦ - return processed_response - else: - return "MiMo-v2-flash API返回格式错误" - elif response.status_code == 401: - return "MiMo-v2-flash API认证失败,请检查API密钥是否正确配置" - else: - return f"MiMo-v2-flash API调用失败: {response.status_code} - {response.text}" - - # 构建包含历史记录的消息列表 - messages = self._build_messages_with_history(system_prompt, user_input) - - # 构建请求体,使用MiMo特定参数 - body = { - "model": self.model, - "messages": messages, - "temperature": 0.7, - "max_tokens": 2048, - "top_p": 0.95, - "frequency_penalty": 0, - "presence_penalty": 0, - # MiMo特定参数 - "extra_body": { - "thinking": {"type": "disabled"} # 禁用思考模式 - } - } - - # 直接使用requests调用MiMo API,因为需要特定的header格式 - import requests - response = requests.post( - url=self.base_url, - json=body, - headers=self.headers, - timeout=None - ) - - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - # 更新对话历史 - self._update_conversation_history(user_input, ai_response) - - # 返回处理后的AI回复 - return processed_response - else: - return "MiMo-v2-flash API返回格式错误" - elif response.status_code == 401: - return "MiMo-v2-flash API认证失败,请检查API密钥是否正确配置" - else: - return f"MiMo-v2-flash API调用失败: {response.status_code} - {response.text}" - - except Exception as e: - # 如果API调用失败,返回错误信息 - return f"MiMo-v2-flash API调用失败: {str(e)}" - - def _build_messages_with_history(self, system_prompt, user_input): - """构建包含历史记录的消息列表""" - messages = [] - - # 添加系统提示词 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加历史对话记录(使用共享历史) - if self.shared_conversation_history is not None: - # 确保历史记录格式正确 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正确,跳过该条记录 - continue - - # 添加当前用户输入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - def _update_conversation_history(self, user_input, ai_response): - """更新对话历史""" - # 添加用户输入到历史记录 - self.conversation_history.append({ - "role": "user", - "content": user_input - }) - # 添加AI回复到历史记录 - self.conversation_history.append({ - "role": "assistant", - "content": ai_response - }) - # 限制历史记录数量 - if len(self.conversation_history) > self.max_history * 2: - self.conversation_history = self.conversation_history[-self.max_history * 2:] - - def _call_api_with_history(self, content, system_prompt=None): - """使用对话历史调用API""" - # 获取当前日期和星期 - now = datetime.now() - date_str = now.strftime("%Y年%m月%d日") - week_str = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"][now.weekday()] - current_datetime = f"{date_str} {week_str}" - - # 设置默认系统提示词(如果未提供) - if not system_prompt: - system_prompt = f"You are MiMo, an AI assistant developed by Xiaomi. Today is date: {current_datetime}. Your knowledge cutoff date is December 2024." - - # 构建消息列表 - messages = [] - - # 添加系统提示词(如果提供) - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加历史对话记录(使用共享历史) - if self.shared_conversation_history is not None: - # 确保历史记录格式正确 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正确,跳过该条记录 - continue - - # 添加当前内容 - messages.append({ - "role": "user", - "content": content - }) - - # 构建请求体 - body = { - "model": self.model, - "messages": messages, - "max_completion_tokens": 1024, - "temperature": 0.3, - "top_p": 0.95, - "stream": False, - "frequency_penalty": 0, - "presence_penalty": 0, - "extra_body": { - "thinking": {"type": "disabled"} - } - } - - # 发送请求到MiMo-v2-flash API - response = requests.post(url=self.base_url, json=body, headers=self.headers, timeout=None) - - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - return processed_response - else: - return "MiMo-v2-flash API返回格式错误" - else: - return f"MiMo-v2-flash API调用失败: {response.status_code} - {response.text}" - - def handle_command(self, command): - """处理引擎特定命令""" - # MiMo引擎目前不支持特定命令 - print(f"{Fore.RED}MiMo引擎不支持此命令{Style.RESET_ALL}") - return False - - def _process_thinking_content(self, response): - """处理思考内容,将思考部分以灰色文本显示,只返回回复部分""" - from colorama import Fore, Style - import re - - # 这里可以设置思考标记,由用户自己定义 - thinking_start_marker = "" - thinking_end_marker = "" - - # 如果响应中包含思考标记,则提取思考内容和回复内容 - if thinking_start_marker in response and thinking_end_marker in response: - # 提取思考内容 - thinking_match = re.search(f'{thinking_start_marker}(.*?){thinking_end_marker}', response, re.DOTALL) - if thinking_match: - thinking_content = thinking_match.group(1).strip() - # 打印思考内容(灰色) - print(f"{Fore.LIGHTBLACK_EX}思考: {thinking_content}{Style.RESET_ALL}") - - # 提取回复内容 - response_content = re.split(f'{thinking_end_marker}', response, maxsplit=1)[-1].strip() - return response_content - - # 如果没有思考标记,直接返回响应 - return response diff --git a/ai_engines/openai_engine.py b/ai_engines/openai_engine.py new file mode 100644 index 0000000..9a0d2dd --- /dev/null +++ b/ai_engines/openai_engine.py @@ -0,0 +1,332 @@ +""" +OpenAI 兼容格式引擎 +支持所有兼容 OpenAI API 格式的服务(如 DeepSeek、Grok、硅基流动、本地 vLLM 等) +""" + +import os +import sys +import json +import requests +from colorama import Fore, Style +import logging + +# 设置日志 +logger = logging.getLogger(__name__) + +# 添加项目根目录到sys.path +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.dirname(current_dir) +sys.path.insert(0, project_root) + + +class OpenaiAI: + """OpenAI 兼容格式 AI 引擎 - 支持所有兼容 OpenAI API 的服务""" + requires_api_key = True + + def get_help_info(self): + """获取引擎帮助信息""" + help_text = """ +OpenAI 兼容格式引擎帮助信息 +============================ + +引擎名称: openai +描述: 通用 OpenAI API 兼容引擎,支持所有兼容 OpenAI 格式的服务 + +支持的服务: + - OpenAI 官方 API + - DeepSeek API + - Grok (xAI) API + - 硅基流动 (SiliconFlow) API + - 本地 vLLM / LiteLLM / OneAPI 等转发服务 + - 任何兼容 /chat/completions 接口的服务 + +配置方式 (config.json): + { + "api": { + "engines": { + "openai": { + "api_key": "你的API密钥", + "base_url": "https://api.openai.com/v1", + "model": "gpt-4o" + } + } + } + } + +可用命令: + /engine.openai models - 列出可用模型(需要服务端支持) + /engine.openai set <模型名> - 切换模型 + /engine.openai info - 显示当前配置信息 + +使用说明: +1. 在 config.json 中配置 api_key、base_url、model +2. 使用 /engine switch openai 切换到此引擎 +3. 直接输入自然语言与 AI 对话 + +注意: + - base_url 填写 API 的基础地址,无需包含 /chat/completions + - 例如 OpenAI 官方填 https://api.openai.com/v1 + - 本地服务填 http://localhost:8000/v1 +""" + return help_text + + def __init__(self): + self.name = "openai" + self.cli = None + + # 从 config.json 读取配置 + config_file = os.path.join(project_root, "config.json") + try: + with open(config_file, 'r', encoding='utf-8') as f: + config = json.load(f) + engine_config = config.get("api", {}).get("engines", {}).get("openai", {}) + self.api_key = engine_config.get("api_key", "") + self.base_url = engine_config.get("base_url", "").rstrip('/') + self.model = engine_config.get("model", "") + self.max_history = config.get("system", {}).get("max_history", 10) + except Exception as e: + logger.warning(f"读取配置文件失败: {e}") + self.api_key = "" + self.base_url = "" + self.model = "" + self.max_history = 10 + + # 初始化对话历史 + self.conversation_history = [] + self.shared_conversation_history = None + + # 思考标记(兼容深度思考模型) + self.thinking_start_marker = "" + self.thinking_end_marker = "" + + # 检查配置 + if not self.api_key: + print(f"{Fore.YELLOW}警告: openai 引擎的 API 密钥未设置{Style.RESET_ALL}") + if not self.base_url: + print(f"{Fore.YELLOW}警告: openai 引擎的 base_url 未设置{Style.RESET_ALL}") + else: + print(f"{Fore.GREEN}OpenAI 兼容引擎已启用 (base_url: {self.base_url}){Style.RESET_ALL}") + + def _get_headers(self): + """构建请求头""" + headers = { + 'Content-Type': 'application/json' + } + if self.api_key: + headers['Authorization'] = f'Bearer {self.api_key}' + return headers + + def _get_api_url(self): + """获取完整的 API 地址""" + url = self.base_url + if not url: + return "" + if not url.endswith('/chat/completions'): + url = url.rstrip('/') + '/chat/completions' + return url + + def generate_response(self, user_input, tool_results=None, system_prompt=None): + """生成 AI 响应""" + try: + if not self.base_url: + return "错误: openai 引擎的 base_url 未配置,请在 config.json 中设置" + + # 处理工具结果 + if tool_results: + tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}" + messages = self._build_messages_with_history(system_prompt, tool_result_text) + else: + messages = self._build_messages_with_history(system_prompt, user_input) + + # 构建请求体 + body = { + "model": self.model, + "messages": messages, + "stream": False + } + + # 发送请求 + api_url = self._get_api_url() + response = requests.post( + url=api_url, + json=body, + headers=self._get_headers(), + timeout=None + ) + + if response.status_code == 200: + response_data = response.json() + if "choices" in response_data and len(response_data["choices"]) > 0: + ai_response = response_data["choices"][0]["message"]["content"] + # 清理无效的 UTF-8 代理字符 + ai_response = ai_response.encode('utf-8', 'replace').decode('utf-8') + # 处理思考内容 + ai_response = self._process_thinking_content(ai_response) + return ai_response + else: + return f"API 返回格式错误: {response_data}" + elif response.status_code == 401: + return "API 认证失败,请检查 api_key 是否正确" + elif response.status_code == 404: + return f"API 端点不存在,请检查 base_url 是否正确: {api_url}" + else: + return f"API 调用失败: {response.status_code} - {response.text}" + + except requests.exceptions.ConnectionError: + return f"无法连接到 API 服务: {self.base_url},请检查服务是否运行" + except Exception as e: + return f"API 调用失败: {str(e)}" + + def _build_messages_with_history(self, system_prompt, user_input): + """构建包含历史记录的消息列表""" + messages = [] + + # 添加系统提示词 + if system_prompt: + clean_prompt = system_prompt.encode('utf-8', 'replace').decode('utf-8') + messages.append({ + "role": "system", + "content": clean_prompt + }) + + # 添加历史对话记录(使用共享历史) + if self.shared_conversation_history is not None: + for msg in self.shared_conversation_history[-self.max_history:]: + if isinstance(msg, dict) and "role" in msg and "content" in msg: + clean_content = msg["content"].encode('utf-8', 'replace').decode('utf-8') + messages.append({"role": msg["role"], "content": clean_content}) + else: + continue + + # 添加当前用户输入 + if user_input: + clean_input = user_input.encode('utf-8', 'replace').decode('utf-8') + messages.append({ + "role": "user", + "content": clean_input + }) + + return messages + + def _call_api_with_history(self, content, system_prompt=None): + """使用对话历史调用 API""" + if not self.base_url: + return "错误: openai 引擎的 base_url 未配置" + + messages = [] + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + if self.shared_conversation_history is not None: + for msg in self.shared_conversation_history[-self.max_history:]: + if isinstance(msg, dict) and "role" in msg and "content" in msg: + messages.append(msg) + messages.append({"role": "user", "content": content}) + + body = { + "model": self.model, + "messages": messages, + "stream": False + } + + try: + response = requests.post( + url=self._get_api_url(), + json=body, + headers=self._get_headers(), + timeout=None + ) + + if response.status_code == 200: + response_data = response.json() + if "choices" in response_data and len(response_data["choices"]) > 0: + ai_response = response_data["choices"][0]["message"]["content"] + ai_response = ai_response.encode('utf-8', 'replace').decode('utf-8') + return self._process_thinking_content(ai_response) + return f"API 调用失败: {response.status_code} - {response.text}" + except Exception as e: + return f"API 调用失败: {str(e)}" + + def _process_thinking_content(self, response): + """处理思考内容,将思考部分以灰色文本显示""" + import re + + if not self.thinking_start_marker or not self.thinking_end_marker: + return response + + if self.thinking_start_marker in response and self.thinking_end_marker in response: + parts = response.split(self.thinking_start_marker) + if len(parts) > 1: + remaining = parts[1] + if self.thinking_end_marker in remaining: + thinking_part, reply_part = remaining.split(self.thinking_end_marker, 1) + thinking_content = thinking_part.strip() + reply_content = reply_part.strip() + if thinking_content: + print(f"{Fore.LIGHTBLACK_EX}[思考: {thinking_content}]{Style.RESET_ALL}") + return reply_content if reply_content else "" + + return response + + def set_model(self, model_name): + """设置模型""" + self.model = model_name + print(f"{Fore.GREEN}已切换模型为: {model_name}{Style.RESET_ALL}") + + def list_models(self): + """列出可用模型(调用 API 的 /models 端点)""" + if not self.base_url: + return ["错误: base_url 未配置"] + + try: + models_url = self.base_url.rstrip('/') + if models_url.endswith('/chat/completions'): + models_url = models_url[:-len('/chat/completions')] + models_url = models_url.rstrip('/') + '/models' + + response = requests.get( + url=models_url, + headers=self._get_headers(), + timeout=10 + ) + + if response.status_code == 200: + data = response.json() + if "data" in data: + models = [m.get("id", "unknown") for m in data["data"]] + return models + else: + return ["API 返回格式不标准,无法解析模型列表"] + else: + return [f"获取模型列表失败: {response.status_code}"] + except Exception as e: + return [f"获取模型列表失败: {str(e)}"] + + def handle_command(self, command): + """处理引擎特定命令""" + if command == "models": + models = self.list_models() + print(f"{Fore.GREEN}可用模型列表:{Style.RESET_ALL}") + for model in models: + status = " (当前使用)" if model == self.model else "" + print(f"{Fore.GREEN} - {model}{status}{Style.RESET_ALL}") + return True + + elif command.startswith("set "): + model_name = command[4:].strip() + if model_name: + self.set_model(model_name) + return True + else: + print(f"{Fore.RED}请提供模型名称. 用法: /engine.openai set <模型名>{Style.RESET_ALL}") + return False + + elif command == "info": + print(f"{Fore.GREEN}OpenAI 兼容引擎配置:{Style.RESET_ALL}") + print(f" base_url: {self.base_url}") + print(f" model: {self.model}") + print(f" api_key: {'已设置' if self.api_key else '未设置'}") + return True + + else: + print(f"{Fore.RED}未知命令. 可用命令: models, set, info{Style.RESET_ALL}") + return False diff --git a/ai_engines/qwq_engine.py b/ai_engines/qwq_engine.py deleted file mode 100644 index eee52eb..0000000 --- a/ai_engines/qwq_engine.py +++ /dev/null @@ -1,291 +0,0 @@ -import os -import json -import requests -import threading -import time -from colorama import Fore, Style - - -class QwqAI: - """基于QwQ-32B模型的AI引擎""" - - def get_help_info(self): - """获取引擎帮助信息""" - help_text = """ -QwQ-32B AI引擎插件帮助信息 -========================== - -引擎名称: QwQ -描述: 基于QwQ-32B模型的AI引擎,支持复杂推理和数学计算 - -可用命令: - 无特定命令,直接与AI对话即可 - -使用说明: -1. 此引擎使用QwQ-32B云API -2. 使用/engine.switch qwq切换到此引擎 -3. 直接输入自然语言与AI对话 -4. 特别适合处理数学、逻辑推理等复杂任务 -""" - return help_text - - def __init__(self): - self.name = "qwq" - self.cli = None # 引用CLI实例以访问插件 - - # 直接从 config.json 读取配置 - import json - import os - project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - config_file = os.path.join(project_root, "config.json") - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get("qwq", {}) - self.api_key = engine_config.get("api_key", "") - self.url = engine_config.get("base_url", "") - self.model = engine_config.get("model", "") - except Exception as e: - # 如果读取失败,使用空值 - print(f"{Fore.YELLOW}警告: 读取配置文件失败: {e}{Style.RESET_ALL}") - self.api_key = "" - self.url = "" - self.model = "" - - self.headers = { - 'Authorization': f'Bearer {self.api_key}', - 'Content-Type': 'application/json' - } - # 初始化对话历史 - self.conversation_history = [] - # 设置最大历史记录数 - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - self.max_history = config.get("system", {}).get("max_history", 10) - except: - self.max_history = 10 - - # 设置思考标记 - self.thinking_start_marker = "" - self.thinking_end_marker = "" - - # 共享对话历史引用(由CLI设置) - self.shared_conversation_history = None - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成AI响应 - 使用QwQ API""" - try: - # 如果有工具结果,将其作为上下文返回给AI处理 - if tool_results: - tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}" - # 构建消息列表 - messages = self._build_messages_with_history(system_prompt, tool_result_text) - - # 构建请求体 - body = { - "model": self.model, - "messages": messages, - "stream": False # 使用非流式响应以便于处理 - } - - # 发送请求到QwQ API - response = requests.post(url=self.url, json=body, headers=self.headers, timeout=None) - - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - # 返回处理后的AI回复 - return processed_response - else: - return "QwQ API返回格式错误" - else: - return f"QwQ API调用失败: {response.status_code} - {response.text}" - - # 构建消息列表 - messages = self._build_messages_with_history(system_prompt, user_input) - - # 构建请求体 - body = { - "model": self.model, - "messages": messages, - "stream": False # 使用非流式响应以便于处理 - } - - # 发送请求到QwQ API - response = requests.post(url=self.url, json=body, headers=self.headers, timeout=None) - - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - # 返回处理后的AI回复 - return processed_response - else: - return "QwQ API返回格式错误" - else: - return f"QwQ API调用失败: {response.status_code} - {response.text}" - - except Exception as e: - # 如果API调用失败,返回错误信息 - return f"QwQ API调用失败: {str(e)}" - - def _build_messages_with_history(self, system_prompt, user_input): - """构建包含历史记录的消息列表""" - messages = [] - - # 添加系统提示词 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加历史对话记录(使用共享历史) - if self.shared_conversation_history is not None: - # 确保历史记录格式正确 - for msg in self.shared_conversation_history[-self.max_history:]: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - messages.append(msg) - else: - # 如果格式不正确,跳过该条记录 - continue - - # 添加当前用户输入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - - - def _call_api_with_history(self, content, system_prompt=None): - """使用对话历史调用API""" - # 构建消息列表 - - messages = [] - - # 添加系统提示词(如果提供) - - if system_prompt: - - messages.append({ - - "role": "system", - - "content": system_prompt - - }) - - # 添加历史对话记录(使用共享历史) - - if self.shared_conversation_history is not None: - - # 确保历史记录格式正确 - - for msg in self.shared_conversation_history[-self.max_history:]: - - if isinstance(msg, dict) and "role" in msg and "content" in msg: - - messages.append(msg) - - else: - - # 如果格式不正确,跳过该条记录 - - continue - - # 添加当前内容 - - messages.append({ - - "role": "tool", - - "content": content - - }) - - - - # 构建请求体 - body = { - "model": self.model, - "messages": messages, - "stream": False # 使用非流式响应以便于处理 - } - - # 发送请求到QwQ API - response = requests.post(url=self.url, json=body, headers=self.headers, timeout=None) - - if response.status_code == 200: - response_data = response.json() - if "choices" in response_data and len(response_data["choices"]) > 0: - ai_response = response_data["choices"][0]["message"]["content"] - - # 处理思考标记 - processed_response = self._process_thinking_content(ai_response) - - return processed_response - else: - return "QwQ API返回格式错误" - else: - return f"QwQ API调用失败: {response.status_code} - {response.text}" - - def handle_command(self, command): - """处理引擎特定命令""" - # QwQ引擎目前不支持特定命令 - print(f"{Fore.RED}QwQ引擎不支持此命令{Style.RESET_ALL}") - return False - - def _process_thinking_content(self, response): - """处理思考内容,将思考部分以灰色文本显示,只返回回复部分""" - from colorama import Fore, Style - import re - - # 这里可以设置思考标记,由用户自己定义 - thinking_start_marker = "" # 用户可以设置开始标记 - thinking_end_marker = "" # 用户可以设置结束标记 - - # 如果用户设置了思考标记,则处理思考内容 - if thinking_start_marker and thinking_end_marker and thinking_start_marker in response and thinking_end_marker in response: - # 分离思考内容和回复内容 - parts = response.split(thinking_start_marker) - if len(parts) > 1: - # 获取包含结束标记的部分 - remaining = parts[1] - thinking_content = "" - reply_content = "" - - # 检查结束标记 - if thinking_end_marker in remaining: - thinking_part, reply_part = remaining.split(thinking_end_marker, 1) - thinking_content = thinking_part.strip() - reply_content = reply_part.strip() - else: - # 如果没有找到结束标记,将剩余部分都视为思考内容 - thinking_content = remaining.strip() - reply_content = "" - - # 显示思考内容为灰色文本 - if thinking_content: - print(f"{Fore.LIGHTBLACK_EX}[思考: {thinking_content}]{Style.RESET_ALL}") - - # 返回回复内容 - if reply_content: - return reply_content - else: - return "" # 如果没有回复内容,返回空字符串 - else: - # 如果没有设置思考标记或没有找到标记,直接返回原始响应 - return response \ No newline at end of file diff --git a/ai_engines/xfyun_spark_engine.py b/ai_engines/xfyun_spark_engine.py deleted file mode 100644 index 7f69da5..0000000 --- a/ai_engines/xfyun_spark_engine.py +++ /dev/null @@ -1,275 +0,0 @@ -import os -import sys -import json -import hashlib -import hmac -import base64 -import websocket -import time -import threading -from datetime import datetime -from time import mktime -from urllib.parse import urlparse, urlencode -from wsgiref.handlers import format_date_time -from colorama import Fore, Style - -# 获取项目根目录 -current_dir = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(current_dir) -sys.path.insert(0, project_root) - - -class XfyunSparkAI: - """基于讯飞星火深度推理X2模型的AI引擎(WebSocket接口)""" - requires_api_key = True # 讯飞引擎需要API密钥 - - # X2深度推理版本固定配置 - URL = "wss://spark-api.xf-yun.com/x2" - DOMAIN = "spark-x" - MAX_TOKENS = 131072 # X2输出上限128K - - def __init__(self): - self.name = "xfyun-spark" - self.cli = None - - # 直接从 config.json 读取配置 - config_file = os.path.join(project_root, "config.json") - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - engine_config = config.get("api", {}).get("engines", {}).get("xfyun_spark", {}) - self.app_id = engine_config.get("app_id", "") - self.api_key = engine_config.get("api_key", "") - self.api_secret = engine_config.get("api_secret", "") - self.max_history = config.get("system", {}).get("max_history", 10) - except Exception as e: - self.app_id = "" - self.api_key = "" - self.api_secret = "" - self.max_history = 10 - - # 初始化对话历史 - self.conversation_history = [] - self.shared_conversation_history = None - self.chat_id = f"chat_{self.app_id}_{int(time.time())}" # 固定会话ID,确保上下文连续性 - - # 验证API密钥 - if not self.app_id or not self.api_key or not self.api_secret: - print(f"{Fore.YELLOW}警告: xfyun-spark引擎的API配置未完整设置(需要app_id、api_key、api_secret){Style.RESET_ALL}") - - def get_help_info(self): - """获取引擎帮助信息""" - help_text = f""" -讯飞星火深度推理X2引擎帮助信息 -================================ - -引擎名称: xfyun-spark -描述: 基于讯飞星火深度推理X2模型的AI引擎,支持WebSocket流式输出 - -版本: X2深度推理 (输入64K, 输出128K) - -配置要求: - 在config.json中配置: - {{ - "api": {{ - "engines": {{ - "xfyun_spark": {{ - "app_id": "你的应用ID", - "api_key": "你的API Key", - "api_secret": "你的API Secret" - }} - }} - }} - }} - -使用说明: -1. 使用/engine.switch xfyun-spark切换到此引擎 -2. 直接输入自然语言与AI对话 -3. 本引擎强制使用流式输出,AI回复会实时显示 -4. 支持深度思考模式,思考过程会自动显示 - -注意: 本引擎使用WebSocket协议,需要app_id、api_key、api_secret三个参数 -""" - return help_text - - def _generate_url(self): - """生成鉴权URL(使用官方示例的方式)""" - # 生成RFC1123格式的时间戳 - now = datetime.now() - date = format_date_time(mktime(now.timetuple())) - - # 解析URL获取host和path - parsed_url = urlparse(self.URL) - host = parsed_url.netloc - path = parsed_url.path - - # 拼接签名字符串 - signature_origin = "host: " + host + "\n" - signature_origin += "date: " + date + "\n" - signature_origin += "GET " + path + " HTTP/1.1" - - # 进行hmac-sha256进行加密 - signature_sha = hmac.new( - self.api_secret.encode('utf-8'), - signature_origin.encode('utf-8'), - digestmod=hashlib.sha256 - ).digest() - - signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8') - - # 拼接authorization_origin - authorization_origin = f'api_key="{self.api_key}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"' - - authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') - - # 将请求的鉴权参数组合为字典 - v = { - "authorization": authorization, - "date": date, - "host": host - } - - # 拼接鉴权参数,生成url - url = self.URL + '?' + urlencode(v) - - return url - - def _build_messages_with_history(self, system_prompt, user_input): - """构建包含历史记录的消息列表(传递完整历史对话)""" - messages = [] - - # 添加系统提示词 - if system_prompt: - messages.append({ - "role": "system", - "content": system_prompt - }) - - # 添加完整的历史对话(不限制数量,让API自己处理) - if self.shared_conversation_history: - for msg in self.shared_conversation_history: - if isinstance(msg, dict) and "role" in msg and "content" in msg: - role = msg["role"] - content = msg["content"] - # 转换角色名称 - if role == "user": - messages.append({"role": "user", "content": content}) - elif role == "assistant": - messages.append({"role": "assistant", "content": content}) - - # 添加当前用户输入 - messages.append({ - "role": "user", - "content": user_input - }) - - return messages - - def generate_response(self, user_input, tool_results=None, system_prompt=None): - """生成AI响应(非流式)""" - try: - # 处理工具结果 - if tool_results: - tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}" - messages = self._build_messages_with_history(system_prompt, tool_result_text) - else: - messages = self._build_messages_with_history(system_prompt, user_input) - - # 构建请求数据 - request_data = { - "header": { - "app_id": self.app_id, - "uid": str(time.time()).replace('.', '')[:32] - }, - "parameter": { - "chat": { - "domain": self.DOMAIN, - "temperature": 0, - "max_tokens": 4096, - "presence_penalty": 1, - "frequency_penalty": 0.02, - "top_k": 5, - "chat_id": self.chat_id - } - }, - "payload": { - "message": { - "text": messages - } - } - } - - # 生成鉴权URL - auth_url = self._generate_url() - - # 收集完整响应 - full_response = "" - thinking_content = "" - ws = None - result_queue = [] - done_event = threading.Event() - - def on_message(ws, message): - nonlocal full_response, thinking_content - try: - data = json.loads(message) - code = data.get("header", {}).get("code", -1) - - if code != 0: - result_queue.append(("error", data.get('header', {}).get('message', '未知错误'))) - done_event.set() - return - - payload = data.get("payload", {}) - choices = payload.get("choices", {}) - - # 处理文本响应 - text_list = choices.get("text", []) - if text_list: - reasoning = text_list[0].get("reasoning_content", "") - if reasoning: - thinking_content += reasoning - content = text_list[0].get("content", "") - if content: - full_response += content - - if choices.get("status") == 2: - done_event.set() - - except Exception as e: - result_queue.append(("error", str(e))) - done_event.set() - - def on_error(ws, error): - result_queue.append(("error", str(error))) - done_event.set() - - def on_close(ws, close_status_code, close_msg): - pass - - def on_open(ws): - ws.send(json.dumps(request_data)) - - # 创建WebSocket连接 - ws = websocket.WebSocketApp( - auth_url, - on_open=on_open, - on_message=on_message, - on_error=on_error, - on_close=on_close - ) - - # 在后台线程运行WebSocket - ws_thread = threading.Thread(target=ws.run_forever) - ws_thread.daemon = True - ws_thread.start() - - # 等待完成 - done_event.wait(timeout=60) - - # 返回完整响应 - return full_response - - except Exception as e: - print(f"\n{Fore.RED}讯飞星火API调用失败: {str(e)}{Style.RESET_ALL}") - return f"讯飞星火API调用失败: {str(e)}" \ No newline at end of file diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..c09eb28 --- /dev/null +++ b/config.json.example @@ -0,0 +1,20 @@ +{ + "api": { + "engines": { + "ollama": { + "api_key": "", + "base_url": "http://localhost:11434", + "model": "qwen2.5:latest" + }, + "openai": { + "api_key": "your-api-key-here", + "base_url": "https://api.deepseek.com/v1", + "model": "deepseek-chat" + } + } + }, + "system": { + "default_engine": "ollama", + "max_history": 50 + } +}