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:
commit
e22652fbbb
53 files changed
+20067
No files matched your search
@@ -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
|
||||
@@ -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'
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)}"
|
||||
Reference in new issue
Block a user