feat: 增强编码能力 - 新增代码编辑器、代码搜索、Git集成

- 新增 plugins/code_editor.py: 精准代码编辑(搜索替换、批量编辑、diff对比)
- 新增 plugins/code_search.py: 代码搜索与理解(跨文件搜索、符号提取、依赖分析)
- 新增 plugins/git_tools.py: Git 版本控制(status/diff/log/commit/branch)
- 新增 core/ 模块: 配置管理、引擎管理、对话处理、显示输出、代码沙箱
- 修复 requirements.txt: 使用正确的 pip 包名(Pillow、opencv-python 等)
- 更新 unified_tool_manager.py: 兼容 Plugin 和 Liugin 两种类名
- 更新系统提示词: 增强编程工作流指导

本次更新由 mimo 自动化执行,与开发者无关。
This commit is contained in:
Xiaoli Dev committed 2026-04-25 10:55:27 +08:00
commit e22652fbbb
53 files changed
+20067

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
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
+266
View File
@@ -0,0 +1,266 @@
"""
小狸 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="<thinking>", end_marker="</thinking>", show_thinking=True):
"""
设置深度思考引擎配置
Args:
enable: 是否启用深度思考引擎模式
start_marker: 思考开始标记,默认为 "<thinking>"
end_marker: 思考结束标记,默认为 "</thinking>"
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'
]
+405
View File
@@ -0,0 +1,405 @@
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
+205
View File
@@ -0,0 +1,205 @@
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
+355
View File
@@ -0,0 +1,355 @@
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>"
thinking_end_marker = "</thinking>"
# 如果响应中包含思考标记,则提取思考内容和回复内容
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
+401
View File
@@ -0,0 +1,401 @@
import os
import sys
import json
import requests
import threading
import time
from colorama import Fore, Style
# 延迟导入 ollama,避免程序启动时卡住
# 如果导入失败,ollama_client 将为 None
try:
import ollama
ollama_client = ollama
except Exception as e:
print(f"{Fore.YELLOW}警告: 无法导入 ollama 库: {e}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}请确保已安装 ollama 包 (pip install ollama){Style.RESET_ALL}")
ollama_client = None
# 添加项目根目录到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 OllamaAI:
"""基于Ollama的本地AI引擎"""
requires_api_key = False # Ollama是本地引擎,不需要API密钥
def get_help_info(self):
"""获取引擎帮助信息"""
help_text = """
Ollama AI引擎插件帮助信息
========================
引擎名称: Ollama
描述: 基于Ollama的本地AI引擎,支持多种大型语言模型
可用命令:
/engine.ollama models - 列出本地可用的模型
/engine.ollama remote-models - 显示可下载的模型列表
/engine.ollama search <关键词> - 搜索可下载的模型
/engine.ollama set <模型名> - 设置当前使用的模型
/engine.ollama pull <模型名> - 从Ollama库拉取模型
注意: 本引擎强制使用流式输出,AI回复会实时显示。
使用说明:
1. 确保Ollama服务已在本地运行
2. 使用/engine.switch ollama切换到此引擎
3. 使用上述命令管理模型和与AI交互
思考模式:
- 支持深度思考模型(如 QwQ、DeepSeek-R1)
- 思考过程会自动以灰色文本显示
"""
return help_text
def __init__(self):
self.name = "ollama"
self.cli = None # 引用CLI实例以访问插件
# 初始化对话历史
self.conversation_history = []
# 直接从 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("ollama", {})
self.api_key = engine_config.get("api_key", "")
self.base_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:
# 如果读取失败,使用空值
self.api_key = ""
self.base_url = ""
self.model = ""
self.max_history = 10
# 共享对话历史引用(由CLI设置)
self.shared_conversation_history = None
# 直接启用思考标记,由主程序自动处理深度思考模型的响应
self.thinking_start_marker = "<thinking>"
self.thinking_end_marker = "</thinking>"
# 检测Ollama服务是否运行
self.is_service_running = self.check_ollama_service()
if self.is_service_running:
print(f"{Fore.GREEN}Ollama服务检测成功,引擎已启用{Style.RESET_ALL}")
else:
print(f"{Fore.RED}Ollama服务未运行,引擎将不可用{Style.RESET_ALL}")
def check_ollama_service(self):
"""检测Ollama服务是否运行"""
# 检查 ollama 库是否可用
if ollama_client is None:
return False
try:
# 尝试调用Ollama API获取版本信息
response = ollama_client.list()
# 如果没有异常,说明服务运行正常
return True
except Exception as e:
# 如果出现异常,说明服务未运行
return False
def generate_response(self, user_input, tool_results=None, system_prompt=None):
"""生成AI响应 - 使用Ollama API"""
try:
# 检查Ollama服务是否运行
if not self.is_service_running:
return "错误:Ollama服务未运行,请启动Ollama服务后再使用此引擎."
# 如果有工具结果,将其作为上下文返回给AI处理
if tool_results:
tool_result_text = f"工具执行结果: {tool_results.get('result', '无结果')}"
# 构建包含历史记录的消息列表
messages = self._build_messages_with_history(system_prompt, tool_result_text)
# 使用Ollama Python库调用API
response = ollama_client.chat(
model=self.model,
messages=messages,
stream=False
)
# 返回AI的回复
return response["message"]["content"]
# 构建包含历史记录的消息列表
messages = self._build_messages_with_history(system_prompt, user_input)
# 使用Ollama Python库调用API
response = ollama_client.chat(
model=self.model,
messages=messages,
stream=False
)
# 返回AI的回复
content = response["message"]["content"]
# 清理无效的 UTF-8 代理字符
content = content.encode('utf-8', 'replace').decode('utf-8')
return content
except Exception as e:
# 如果API调用失败,返回错误信息
return f"Ollama 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.is_service_running:
return "错误:Ollama服务未运行,请启动Ollama服务后再使用此引擎."
# 构建消息列表
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
})
# 使用Ollama Python库调用API
response = ollama_client.chat(
model=self.model,
messages=messages,
stream=False
)
return response["message"]["content"]
def set_model(self, model_name):
"""设置要使用的模型"""
if not self.is_service_running:
print(f"{Fore.RED}错误:Ollama服务未运行,无法切换模型{Style.RESET_ALL}")
return
self.model = model_name
print(f"{Fore.GREEN}已切换Ollama模型为: {model_name}{Style.RESET_ALL}")
def list_models(self):
"""列出本地可用的模型"""
if not self.is_service_running:
return ["错误:Ollama服务未运行"]
try:
response = ollama_client.list()
models = [model["name"] for model in response["models"]]
return models
except Exception as e:
return [f"获取模型列表失败: {str(e)}"]
def list_remote_models(self, limit=10):
"""获取可下载的模型列表(仅展示前N个)"""
if not self.is_service_running:
return ["错误:Ollama服务未运行"]
try:
# 使用Ollama Python库获取可下载的模型列表
# 注意:Ollama库本身没有直接获取可下载模型列表的API
# 这里返回一些常见的模型作为示例
# 实际应用中可能需要从Ollama官网或其他来源获取此信息
common_models = [
"llama3.1:latest",
"llama3:latest",
"mistral:latest",
"gemma2:latest",
"phi3:latest",
"qwen2:latest",
"command-r:latest",
"dolphin-mistral:latest",
"nous-hermes2:latest",
"llava:latest",
"codellama:latest",
"mxbai-embed-large:latest",
"all-minilm:latest",
"stable-diffusion:latest",
"moondream:latest"
]
return common_models[:limit]
except Exception as e:
return [f"获取远程模型列表失败: {str(e)}"]
def search_models(self, query, limit=10):
"""模糊搜索可下载的模型"""
if not self.is_service_running:
return ["错误:Ollama服务未运行"]
try:
# 使用Ollama Python库获取可下载的模型列表
# 注意:Ollama库本身没有直接获取可下载模型列表的API
all_models = [
"llama3.1:latest", "llama3:latest", "llama2:latest",
"mistral:latest", "mistral-openorca:latest",
"gemma2:latest", "gemma:latest",
"phi3:latest", "phi2:latest",
"qwen2:latest", "qwen:latest",
"command-r:latest", "command-r-plus:latest",
"dolphin-mistral:latest", "dolphin-llama3:latest",
"nous-hermes2:latest", "nous-hermes:latest",
"llava:latest", "bakllava:latest",
"codellama:latest", "codeup:latest",
"mxbai-embed-large:latest", "nomic-embed-text:latest",
"all-minilm:latest", "bge-m3:latest",
"stable-diffusion:latest", "moondream:latest",
"llama3-groq:latest", "eva-qwen2:latest"
]
# 进行模糊搜索(简单匹配,包含查询词的模型)
matched_models = [model for model in all_models if query.lower() in model.lower()]
# 限制返回数量
return matched_models[:limit]
except Exception as e:
return [f"搜索模型失败: {str(e)}"]
def pull_model(self, model_name):
"""从Ollama库拉取模型"""
if not self.is_service_running:
print(f"{Fore.RED}错误:Ollama服务未运行,无法拉取模型{Style.RESET_ALL}")
return False
try:
print(f"{Fore.YELLOW}正在拉取模型 {model_name}...{Style.RESET_ALL}")
# 使用Ollama Python库拉取模型
response = ollama_client.pull(model_name, stream=True)
# 逐块处理响应
for part in response:
if 'status' in part:
status = part['status']
print(f"{Fore.YELLOW}{status}{Style.RESET_ALL}")
# 检查是否完成
if 'completed' in part and 'total' in part:
if part['completed'] == part['total']:
print(f"{Fore.GREEN}模型 {model_name} 拉取完成!{Style.RESET_ALL}")
print(f"{Fore.GREEN}模型 {model_name} 拉取成功!{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}拉取模型 {model_name} 失败: {str(e)}{Style.RESET_ALL}")
return False
def handle_command(self, command):
"""处理引擎特定命令"""
if not self.is_service_running:
print(f"{Fore.RED}Ollama服务未运行,无法执行命令{Style.RESET_ALL}")
return False
if command == "models":
# 显示本地可用模型
models = self.list_models()
print(f"{Fore.GREEN}本地可用的Ollama模型:{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 == "remote-models":
# 显示可下载的模型(前10个)
models = self.list_remote_models()
print(f"{Fore.GREEN}可下载的Ollama模型(前10个):{Style.RESET_ALL}")
for i, model in enumerate(models, 1):
print(f"{Fore.GREEN} {i}. {model}{Style.RESET_ALL}")
return True
elif command.startswith("search "):
# 搜索模型
query = command[7:].strip() # 移除 'search '
if query:
models = self.search_models(query)
if models:
print(f"{Fore.GREEN}搜索 '{query}' 的结果(前10个):{Style.RESET_ALL}")
for i, model in enumerate(models, 1):
print(f"{Fore.GREEN} {i}. {model}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}未找到与 '{query}' 匹配的模型{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}请提供搜索关键词.用法: /engine.search <关键词>{Style.RESET_ALL}")
return False
elif command.startswith("set "):
# 设置模型
model_name = command[4:].strip() # 移除 'set '
if model_name:
self.set_model(model_name)
return True
else:
print(f"{Fore.RED}请提供模型名称.用法: /engine.set <模型名>{Style.RESET_ALL}")
return False
elif command.startswith("pull "):
# 拉取模型
model_name = command[5:].strip() # 移除 'pull '
if model_name:
success = self.pull_model(model_name)
return success
else:
print(f"{Fore.RED}请提供模型名称.用法: /engine.pull <模型名>{Style.RESET_ALL}")
return False
else:
print(f"{Fore.RED}未知的Ollama命令.可用命令: models, remote-models, search, set, pull{Style.RESET_ALL}")
return False
+291
View File
@@ -0,0 +1,291 @@
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 = "<think>" # 用户可以设置开始标记
thinking_end_marker = "</think>" # 用户可以设置结束标记
# 如果用户设置了思考标记,则处理思考内容
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
+275
View File
@@ -0,0 +1,275 @@
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)}"