- 新增 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 自动化执行,与开发者无关。
275 lines
9.3 KiB
Python
275 lines
9.3 KiB
Python
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)}" |